-- UIDungeonMain
-- create by zouyb
-- 关卡

require "game/ui/form/dungeon/UIGrid"
require "game/ui/form/dungeon/UIDungeonInfo"
require "game/ui/form/spell/UIMagicScrolls"
require "game/ui/form/equips/UIEquips"
require "game/ui/form/hero/UIHeroAttrib"
require "game/ui/form/combat_stat/UICombatStat"
require "game/ui/form/spell/UISelectTarget"
require "game/ui/form/setting/UISystemSetting"
require "game/ui/form/dungeon/UIDragonWish"
require "game/ui/form/dungeon/UIDungeonTopMenu"
require "game/ui/form/dungeon/UIDungeonStatusBar"
require "game/ui/form/dungeon/UIDungeonHpAlert"
require "game/ui/module/hero/HeroDisplay"
require "game/ui/form/dungeon/AutoFight"
require "game/ui/form/dungeon/UIDungeonClearNotify"
require "game/ui/module/bine/BineDisplay"
require "game/ui/form/common/UIBonusNotify"
require "game/ui/form/stone/UIStoneAlert"
require "game/ui/form/common/UICommonAlert"
require "game/ui/guide/UIDungeonGuideMgr"
require "game/ui/buff/MonsterBuffEffectM"
require "game/ui/form/reminder/UIMsgReminder"
require "game/ui/form/dungeon/UIBossEffect"
require "game/ui/form/skill/UISkillEnergyBar"
require "game/ui/form/common/UIImproveNotify"
require "game/ui/module/CommonActionManager"

-- 每个关卡由6行5列的格子组成
GRID_ROW_NUM = 6;
GRID_COL_NUM = 5;
TOTAL_GRID_NUM = GRID_ROW_NUM * GRID_COL_NUM;

-- 格子宽、高
GRID_WIDTH  = 128;
GRID_HEIGHT = 118;

-- 格子的纵向偏移量
GRID_OFFSET_Y = 80;

-- 格子刷怪的延迟
GRID_REFRESH_MONSTER_DELAY   = 0.7

-- UIDungeonMain继承自Layer
UIDungeonMain = class("UIDungeonMain", function()
    return cc.Layer:create();
end);

function UIDungeonMain.create(levelData, layer)
    return UIDungeonMain.new(levelData, layer);
end

-- 是否持续自动操作
stillOpt = false;

-- 迷宫界面创建完毕之后的回调
local alreadyCallback = {};

-- 技能界面
local magicScrollUI = nil;

-- 宝物界面
local equipsUI = nil;

local resize;

-- 构造函数
function UIDungeonMain:ctor(levelData, floor)
    self:setName("UIDungeonMain");

    local node = cc.CSLoader:createNode("layout/dungeon/DungeonMain.csb");
    self:addChild(node);
    self.node = node;

    self:setLocalZOrder(UIMgr.TOP_MENU_ZORDER + 1);

    self.curFloor = floor;
    self.cleared = false;
    self.instanceId = DungeonInstanceM.getCurrentInstanceId();
    self.unrealId = UnrealDungeonM.getUnrealId();

    self.panel = findChildByName(node, "CT1/panel");
    self.attackLabel = findChildByName(self.node, "CT2/atk_val");
    self.magicLabel = findChildByName(self.node, "CT2/magic_val");
    self.hpLabel = findChildByName(self.node, "CT2/hp_val");
    self.mpLabel = findChildByName(self.node, "CT2/mp_val");
    self.specialImage = findChildByName(self.node, "CT2/specil");
    self.gainImage = findChildByName(self.node, "CT2/gain");
    self.debuffsImage = findChildByName(self.node, "CT2/debuffs");

    -- 初始化时记录一下当前的位置
    self.standardPos = cc.p(self.node:getPositionX(), self.node:getPositionY());

    -- 状态条
    self.statusBarNode = findChildByName(self.node, "CT1/status_bar");
    self.statusParentNode = findChildByName(self.node, "CT1/status_bar/parent_node");

    -- 记录一个过滤列表，用来排除已经抽取的小物件列表
    self.filterList = {};

    local function setStyle(textNode)
        TextStyleM.setTextStyle(textNode, TextStyleM.TEXT_SIZE_MIDDLE, TextStyleM.TEXT_COLOR_BROWN_YELLOW, true);
        textNode:setFontSize(28);
        textNode:setTextColor(TextStyleM.TEXT_COLOR_WHITE);
        textNode:getVirtualRenderer():setAdditionalKerning(getLocKerning() -2);
        local outlineColor = TextStyleM.TEXT_OUTLINE_COLOR_DARK;
        textNode:enableOutline(cc.c4b(outlineColor[1], outlineColor[2], outlineColor[3], 255), 2);
        textNode:setScaleX(1.2);
    end

    setStyle(self.attackLabel);
    setStyle(self.magicLabel);
    setStyle(self.hpLabel);
    setStyle(self.mpLabel);

    -- 处于变身状态，创建变身状态的模型，否则创建普通模型
    local shapeId = ShapeChangeM.getShapeChangeId();
    if type(shapeId) == "number" and shapeId ~= -1 then
        -- 创建变身模型
        local pet = ME.user:getActivePet();
        local userPetId = pet.classId;
        self:createShapeChangeModel(shapeId);
        ShapeChangeM.setInShapeChange(true);
    else
        -- 创建英雄模型
        self:createHeroModel();
        ShapeChangeM.setInShapeChange(false);
    end

    -- 保存已打开的怪物格子
    self.openMonsterGrids = {};

    self:updateUI();

    self:registerTouchEvent();

    self:registerEventCallback();

    assert(type(levelData) == 'table', "关卡数据错误");

    -- 根据关卡数据生成地图
    self:generate(levelData);

    resize(node);

    -- 等格子初始化并适配后，再进行滑动Panel的初始化
    self:initSlideTouchPanel();

    -- 检查是否显示称号学习动画
    self:checkCareerAnima();

    -- 初始化顶部菜单
    local topNode = findChildByName(self.node, "TOP");
    self.dungeonTopMenu = UIDungeonTopMenu.create(topNode);

    -- 初始化BUFF状态条
    self.dungeonStatusBar = UIDungeonStatusBar.create();
    -- 记录状态条的初始位置
    self.statusBarInitPos = cc.p(self.statusBarNode:getPosition());
    self.statusParentInitPos = cc.p(self.statusParentNode:getPosition());

    -- 初始化左边的空闲物品栏
    self:initItemBars();

    -- 初始化技能能量槽
    self:initSkillEnergyBar();

    -- 初始化提醒UI
    self:updateMsgReminderUI();

    -- 特殊入场
    self:specialEnterEffect();

    -- 每次初始化先删除
    ME.user.dbase:deleteTemp("reduceSp");

    -- 是重新开始的迷宫
    local restart = ME.user.dbase:queryTemp("restart", 0);
    -- 玩家刚进入地牢，做物品和探索点飞入效果
    if restart == 1 then
        -- 初始化将探索点设置为0
        local spAmount = ME.user:getSp();
        ME.user.dbase:setTemp("reduceSp", spAmount);
        ME.user.dbase:deleteTemp("restart");
        self:getOwnedItems(spAmount);
    end

    -- 出战冈布奥动作播放管理器, 以一个不可见节点存在
    self.heroActionManager = CommonActionManager.create("hero");
    self:addChild(self.heroActionManager);

    -- 迷宫动作播放管理器, 以一个不可见节点存在
    -- 播放时机：每个回合PLAYER_MOVE事件触发后，
    -- 检查所有格子和出战冈布奥的动作是否都已经播放完毕，如果是则开始播放迷宫动作
    -- 典型的应用场景：完美击杀的显示效果
    self.dungeonActionManager = CommonActionManager.create("dungeon");
    self:addChild(self.dungeonActionManager);
end

-- 滑动点击处理代码
require "game/ui/form/dungeon/dungeon/SlideTouchPanel";

-- 创建英雄模型
function UIDungeonMain:createHeroModel()
    -- 获取当前出战英雄
    local hero = ME.user:getActivePet();

    local headImg = findChildByName(self.node, "CT2/head_pic");
    local modelNode = findChildByName(self.node, "CT2/model_node");
    local modelId = hero:getModelId();
    local petModelConfig = PetM.queryModel(modelId);

    local iconPath = getHeroIconPath(hero:getIcon());
    local scale = petModelConfig["model_scale"];
    local offsetX = petModelConfig["offset_x"];
    local offsetY = petModelConfig["offset_y"];

    self.modelNode = createHeroModel(modelId, headImg, modelNode,
        scale, iconPath,offsetX, offsetY);
end

-- 创建英雄变身后的模型
function UIDungeonMain:createShapeChangeModel(shapeId)
    local headImg = findChildByName(self.node, "CT2/head_pic");
    local modelNode = findChildByName(self.node, "CT2/model_node");
    local shapeInfo = ShapeChangeM.query(shapeId);

    -- 获取变身后的宠物
    local iconPath = getHeroIconPath(shapeInfo["icon"]);
    local animation = shapeInfo["animation"];
    local scale = shapeInfo["model_scale"];
    local offsetX = shapeInfo["offset_x"];
    local offsetY = shapeInfo["offset_y"];

    self.modelNode = createShapeChangeMode(shapeId, headImg, animation, modelNode,
        scale, iconPath, offsetX, offsetY);
end

-- 更改模型
function UIDungeonMain:playActivePetShapeChangeEffect(shapeId, isPlayEffect)
    if isPlayEffect then
        -- 播放前置动画
        local shapeInfo = ShapeChangeM.query(shapeId);
        local effectId = shapeInfo["effect_id"];

        -- 播放施法动作
        self:switchHeroAction(HERO_ACTION_CAST_OTHER);

        -- 尝试普通变身发言
        local content = shapeInfo["talk_content"];
        if content and content ~= "" then
            self:showHeroTalk(content);
        end

        -- 获取位置
        local effectParent = UIDungeonMgr:getCurLevel():getEffectParent();
        local monsterNode = findChildByName(self.node, "CT2/model_node");

        -- 转化为effectParent的坐标系
        local worldPos = monsterNode:convertToWorldSpaceAR(cc.p(0, 0));
        local fromPos = effectParent:convertToNodeSpace(worldPos);

        -- 微调
        fromPos.y = fromPos.y - 20;

        -- 创建光效节点
        local effectNode = cc.Node:create();
        effectParent:addChild(effectNode);
        effectNode:setPosition(fromPos);

        -- 变身光效
        playEffect(effectNode, 1841);

        -- 等待一段时间更新模型
        performWithDelay(self.node, function ()
            self:createShapeChangeModel(shapeId);
        end, 1.5);

        return;
    end

    -- 直接播放待机动画
    self:createShapeChangeModel(shapeId);
end

-- 变身还原
function UIDungeonMain:shapeRestore(shapeId, isShowEffect)
    if isShowEffect then
        -- 播放施法动作
        self:switchHeroAction(HERO_ACTION_CAST_OTHER);

        local shapeInfo = ShapeChangeM.query(shapeId);

        -- 尝试变身发言
        local content = shapeInfo["restore_talk"];
        if content then
            self:showHeroTalk(content);
        end

        -- 获取位置
        local effectParent = UIDungeonMgr:getCurLevel():getEffectParent();
        --local uiForm = UIMgr:getCurrentScene():getFormByName("UIDungeonMain");
        local monsterNode = findChildByName(self.node, "CT2/model_node");

        -- 转化为effectParent的坐标系
        local worldPos = monsterNode:convertToWorldSpaceAR(cc.p(0, 0));
        local fromPos = effectParent:convertToNodeSpace(worldPos);

        -- 微调
        fromPos.y = fromPos.y - 20;

        -- 创建光效节点
        local effectNode = cc.Node:create();
        effectParent:addChild(effectNode);
        effectNode:setPosition(fromPos);

        -- 播放烟雾
        performWithDelay(self.node, function ()
            self:playEffect(effectNode, "animation/open_block.csb", 0.2, nil, 1);
        end, 0.1);

        -- 等待一段时间更新模型
        performWithDelay(self.node, function ()
            self:createHeroModel();
        end, 0.8);

        -- 等待一段时间更新模型
        performWithDelay(self.node, function ()
            -- 虚影
            local iconId = shapeInfo["restore_icon"];
            playActivePetShadowEffect(iconId);
        end, 0.9);

        return;
    else
        -- 直接播放待机动画
        self:createHeroModel();
    end
end

-- 切换英雄动作
function UIDungeonMain:switchHeroAction(actionType)
    local heroDisplay = findChildByName(self.modelNode, "HeroDisplay");
    if not heroDisplay then
        return;
    end

    heroDisplay:switchAction(actionType);
end

-- 记录当前应该显示的属性四维相关
function UIDungeonMain:setUserAttribShow(key, value)
    self.userAttribShow = self.userAttribShow or {};
    self.userAttribShow[key] = value;
end

-- 更新UI
function UIDungeonMain:updateUI()
    if DungeonLegacyM.isPlayingLegacy() then
        --cclog("正在播放遗产动画，暂不updateUI。");
        return;
    end

    if self.updateUINode == nil then
        self.updateUINode = cc.Node:create();
        self:addChild(self.updateUINode);
    end

    local function doUpdateUI()
        Profiler.funcBegin("UIDungeonMain:updateUI");

        -- 显示玩家属性
        self.userAttribShow = self.userAttribShow or {};

        local attack = self.userAttribShow["attack"] or ME.user:getAttack();
        local magic = self.userAttribShow["magic"] or ME.user:getMagic();
        local hp = self.userAttribShow["hp"] or ME.user:getHp();
        local mp = self.userAttribShow["mp"] or ME.user:getMp();
        local sp = self.userAttribShow["search_point"] or ME.user:getSp();
        local maxHp = ME.user:getMaxHp();
        local maxMp = ME.user:getMaxMp();

        -- 血量颜色
        self.hpLabel:setColor(AttribColorM.getAttrColor(ME.user, "hp"));

        -- 魔法值颜色
        self.mpLabel:setColor(AttribColorM.getAttrColor(ME.user, "mp"));

        -- 攻击颜色
        self.attackLabel:setColor(AttribColorM.getAttrColor(ME.user, "attack"));

        -- 魔力值颜色
        self.magicLabel:setColor(AttribColorM.getAttrColor(ME.user, "magic"));

        self.attackLabel:setString(tostring(attack));
        self.magicLabel:setString(tostring(magic));
        self.hpLabel:setString(tostring(hp));
        self.mpLabel:setString(tostring(mp));

        self:handlePropEffect();

        if self.dungeonTopMenu then
            self.dungeonTopMenu:updateUI(sp);
        end

        Profiler.funcEnd("UIDungeonMain:updateUI");
    end
    self.updateUINode:stopAllActions();
    performWithDelay(self.updateUINode, doUpdateUI, 0.05);
end

-- 更新英雄血量
function UIDungeonMain:updateHeroHp()
    if ME.user.dbase:queryTemp("delayHpEffect") then
        return;
    end

    if self.updateHeroHpNode == nil then
        self.updateHeroHpNode = cc.Node:create();
        self:addChild(self.updateHeroHpNode);
    end

    local function doUpdateHeroHp()
        Profiler.funcBegin("UIDungeonMain:updateHeroHp");

        local hp = ME.user:getHp();
        self.hpLabel:setColor(AttribColorM.getAttrColor(ME.user, "hp"));
        self.hpLabel:setString(tostring(hp));

        self:handlePropEffect();
        Profiler.funcEnd("UIDungeonMain:updateHeroHp");
    end
    self.updateHeroHpNode:stopAllActions();
    performWithDelay(self.updateHeroHpNode, doUpdateHeroHp, 0.05);
end

-- 更新英雄魔法
function UIDungeonMain:updateHeroMp()
    if ME.user.dbase:queryTemp("delayHpEffect") then
        return;
    end

    if self.updateHeroMpNode == nil then
        self.updateHeroMpNode = cc.Node:create();
        self:addChild(self.updateHeroMpNode);
    end

    local function doUpdateHeroMp()
        Profiler.funcBegin("UIDungeonMain:updateHeroMp");
        local mp = ME.user:getMp();
        self.mpLabel:setColor(AttribColorM.getAttrColor(ME.user, "mp"));
        self.mpLabel:setString(tostring(mp));

        self:handlePropEffect();
        Profiler.funcEnd("UIDungeonMain:updateHeroMp");
    end
    self.updateHeroMpNode:stopAllActions();
    performWithDelay(self.updateHeroMpNode, doUpdateHeroMp, 0.05);
end

-- 更新英雄攻击
function UIDungeonMain:updateHeroAttack()
    if ME.user.dbase:queryTemp("delayHpEffect") then
        return;
    end

    if self.updateHeroAttackNode == nil then
        self.updateHeroAttackNode = cc.Node:create();
        self:addChild(self.updateHeroAttackNode);
    end

    local function doUpdateHeroAttack()
        Profiler.funcBegin("UIDungeonMain:updateHeroAttack");

        local attack = ME.user:getAttack();
        self.attackLabel:setColor(AttribColorM.getAttrColor(ME.user, "attack"));
        self.attackLabel:setString(tostring(attack));

        self:handlePropEffect();
        Profiler.funcEnd("UIDungeonMain:updateHeroAttack");
    end
    self.updateHeroAttackNode:stopAllActions();
    performWithDelay(self.updateHeroAttackNode, doUpdateHeroAttack, 0.05);
end

-- 更新英雄魔力
function UIDungeonMain:updateHeroMagic()
    if ME.user.dbase:queryTemp("delayHpEffect") then
        return;
    end

    if self.updateHeroMagicNode == nil then
        self.updateHeroMagicNode = cc.Node:create();
        self:addChild(self.updateHeroMagicNode);
    end

    local function doUpdateHeroMagic()
        local magic = ME.user:getMagic();
        self.magicLabel:setColor(AttribColorM.getAttrColor(ME.user, "magic"));
        self.magicLabel:setString(tostring(magic));

        self:handlePropEffect();
    end
    self.updateHeroMagicNode:stopAllActions();
    performWithDelay(self.updateHeroMagicNode, doUpdateHeroMagic, 0.05);
end

-- 根据desc更新单个属性
function UIDungeonMain:updateUIByDesc(desc)
    -- 如果是获得了如下属性，单独刷新即可
    if desc == "gain_hp" or desc == "hp" then
        self:updateHeroHp();
        self:updateHeroAttack();
        self:updateHeroMagic();
        self:checkHpAlert();
    elseif desc == "gain_mp" or desc == "mp" then
        self:updateHeroMp();
    elseif desc == "gain_attack" or desc == "attack"  then
        self:updateHeroAttack();
    elseif desc == "gain_magic" or desc == "magic"  then
        self:updateHeroMagic();
    elseif desc == "gain_sp" or desc == "search_point" then
        if self.dungeonTopMenu then
            self.dungeonTopMenu:updateUI();
        end
    elseif desc == "dragon_ball" then
        -- 获得了龙珠
        -- 尝试弹出许愿界面
        self:tryOpenWishForm();
    end
end

-- 同步数据
function UIDungeonMain:syncProps()
    -- 这里保存的是不受Debuff影响的值，防止debuff消失后，出现属性提升效果
    local attack = ME.user:queryAttrib("attack", true);
    local magic = ME.user:queryAttrib("magic", true);
    local maxHp = ME.user:getMaxHp();
    local maxMp = ME.user:getMaxMp();

    self.lastAttack = attack;
    self.lastMagic = magic;
    self.lastMaxHp = maxHp;
    self.lastMaxmp = maxMp;
end

-- 处理属性增加的效果
function UIDungeonMain:handlePropEffect()
    Profiler.funcBegin("UIDungeonMain:handlePropEffect");
    local parentNode = self:getEffectParent();
    local heroPos = self:getHeroPos();

    local ct2 = findChildByName(self.node, "CT2");
    if ct2:isVisible() and not self.isHandlingPropEffect then
        self.isHandlingPropEffect = true;

        local function delayHandle()
            -- 这里获取的是不受Debuff影响的值，防止debuff消失后，出现属性提升效果
            local attack = ME.user:queryAttrib("attack", true);
            local magic = ME.user:queryAttrib("magic", true);
            local maxHp = ME.user:getMaxHp();
            local maxMp = ME.user:getMaxMp();

            local addList = {};

            -- 攻击+N效果
            local delay = 0.01;
            if self.lastAttack ~= nil then
                local addValue =  attack - self.lastAttack;
                if addValue > 0 then
                    table.insert(addList, { "attack", addValue, cc.pAdd(heroPos, cc.p(0, 30)) });
                end
            end
            self.lastAttack = attack;

            -- 魔力+N效果
            if self.lastMagic ~= nil then
                local addValue =  magic - self.lastMagic;
                if addValue > 0 then
                    table.insert(addList, { "magic", addValue, cc.pAdd(heroPos, cc.p(0, 30)) });
                end
            end
            self.lastMagic = magic;

            -- 生命值+N效果
            if self.lastMaxHp ~= nil then
                local addValue =  maxHp - self.lastMaxHp;
                if addValue > 0 then
                    table.insert(addList, { "max_hp", addValue, cc.pAdd(heroPos, cc.p(0, 30)) });
                end
            end
            self.lastMaxHp = maxHp;

            -- 魔法值+N效果
            if self.lastMaxmp ~= nil then
                local addValue =  maxMp - self.lastMaxmp;
                if addValue > 0 then
                    table.insert(addList, { "max_mp", addValue, cc.pAdd(heroPos, cc.p(0, 30)) });
                end
            end
            self.lastMaxmp = maxMp;

            -- 播放特效
            local i = 1;
            for _, arr in pairs(addList) do
                local attrib = arr[1];
                local value  = arr[2];
                local offset = arr[3];
                if value > 0 then
                    local function callFunc()
                        playAttribUpEffect(parentNode, value, offset, attrib);
                    end
                    performWithDelay(ct2, callFunc, 0.01 + 0.8* (i - 1));
                    i = i + 1;
                end
            end

            self.isHandlingPropEffect = false;
        end

        performWithDelay(ct2, delayHandle, 0.01);
    end

    -- 说明当前焦点在别的界面中, 同步下数据， 防止累加老的提升效果
    if not ct2:isVisible() and not self.noSyncProps then
        self:syncProps();
    end

    Profiler.funcEnd("UIDungeonMain:handlePropEffect");
end

-- 初始化左边的空闲物品栏
function UIDungeonMain:initItemBars()
    self.itemBarList = {};
    for i = 1, 5 do
        local itemBar = findChildByName(self.node, "CT1/item_bar"..i);
        local numLabel = itemBar:getChildByName("num");
        TextStyleM.setTextStyle(numLabel, TextStyleM.TEXT_SIZE_SMALL2, TextStyleM.TEXT_COLOR_BEIGE, true, -1);
        itemBar.isBusy = false;
        table.insert(self.itemBarList, itemBar);
    end
end

-- 获取空闲的物品栏
function UIDungeonMain:getFreeItemBar()
    for i = 1, #self.itemBarList do
        local itemBar = self.itemBarList[i];
        if not itemBar.isBusy then
            itemBar.isBusy = true;
            return itemBar;
        end
    end

    -- 没有空闲的，直接取第一个
    return self.itemBarList[1];
end

-- 获取背景格子资源
function UIDungeonMain:getBgPath()
    local r = math.random(1, 12);
    local str = string.format("%02d", r);
    return "images/ui/level/bg/d" .. str .. ".png";
end

-- 背景风格
local path = "images/ui/level/cover/";

local style = "";

-- 特殊的迷宫覆盖资源排列规则
function UIDungeonMain:specialCoverPath(pos, specialType)
    local centerNum = DungeonAreaM.queryCover(style, "centers");

    local prefix = "images/cover/".. style .. "/";

    -- 诸神棋盘
    local path = "";
    if specialType == "chess" then
        local singleNum = DungeonAreaM.queryCover(style, "centers");
        local doubleNum = DungeonAreaM.queryCover(style, "special");
        if pos % 2 == 1 then
            -- 单数是白色的
            local r = math.random(1, singleNum);
            path = "single/" .. r .. ".png";
        else
            -- 双数是黑色的
            local r = math.random(1, doubleNum);
            path = "double/" .. r .. ".png";
        end
    end
    return prefix .. path;
end

-- 获取覆盖层资源
function UIDungeonMain:getCoverPath(pos, specialType)
    -- 如果是特殊的排列方式
    if type(specialType) == "string" then
        return self:specialCoverPath(pos, specialType);
    end

    local centerNum = DungeonAreaM.queryCover(style, "centers");

    local prefix = "images/cover/".. style .. "/";

    local specialIndex = table.indexOf(self.specialPosList, pos);
    local specialIndex2 = table.indexOf(self.specialPosList2, pos);

    local path = "";
    if pos == 1 then
        path = "edge/" .. "left_top.png";
    elseif pos == 5 then
        path = "edge/" .. "right_top.png";
    elseif pos == 26 then
        path = "edge/" .. "left_bottom.png";
    elseif pos == 30 then
        path = "edge/" .. "right_bottom.png";
    elseif math.ceil(pos / GRID_COL_NUM) == 1 then
        path = "edge/" .. "top.png";
    elseif math.ceil(pos / GRID_COL_NUM) == 6 then
        path = "edge/" .. "bottom.png";
    elseif pos % GRID_COL_NUM == 1 then
        path = "edge/" .. "left.png";
    elseif pos % GRID_COL_NUM == 0 then
        path = "edge/" .. "right.png";
    elseif specialIndex ~= -1 then
        -- 是特殊格子显示的位置
        local r = self.specialPosIconList[specialIndex];
        path = "special/" .. r .. ".png";
    elseif specialIndex2 ~= -1 then
        -- 是特殊格子2显示的位置
        local r = self.specialPosIconList2[specialIndex2];
        path = "special2/" .. r .. ".png";
    else
        -- 从center中随机一个
        if centerNum ~= 0 then
            local r = math.random(1, centerNum);
            path = "center/" .. r .. ".png";
        end
    end

    if path ~= "" then
        path = prefix .. path;
    end

    return path;
end

-- 获取覆盖层装饰物资源
function UIDungeonMain:getDecoratePath(pos)
    local prefix = "images/cover/".. style .. "/";
    local coverInfo = DungeonAreaM.queryCover(style);

    if (pos % GRID_COL_NUM > 1) and (pos % GRID_COL_NUM < 5)
        and (math.ceil(pos / GRID_COL_NUM) > 1) and (math.ceil(pos / GRID_COL_NUM) < 6) then
        if not self.hasBigDecorate and pos == self.bigDecorateIndex then
            self.hasBigDecorate = true;
            -- 随机大的装饰物
            if decorateNum ~= 0 then
                local decorateNum = coverInfo.decorates_big;
                local r = math.random(1, decorateNum);
                return prefix .. "decorate/big/" .. r .. ".png";
            end
        end
    end

    local decorateNum = coverInfo.decorates_small;
    local r;
    local fetchTable = {};

    -- 生成待抽取的table
    for i = 1,decorateNum do
        table.insert(fetchTable, i);
    end

    if #self.smallDecorateIndexList > 0 and table.indexOf(self.smallDecorateIndexList, pos) ~= -1 then

        if decorateNum < #self.smallDecorateIndexList then
            -- 数量不足，则随意抽取
            r = math.random(1, decorateNum);
        else
            -- 否则不重复抽取
            r = randomSelectMulti(fetchTable, 1, false, filterList)[1];

            -- 记录已经被抽取过的元素，确保不再被抽取
            table.insert(self.filterList, r);
        end

        table.removeItem(self.smallDecorateIndexList, pos);
        return prefix .. "decorate/small/" .. r .. ".png";
    else
        return "";
    end
end

-- 初始化格子
function UIDungeonMain:generate(levelData)
    -- 大物件 最多一个
    local centerPosList = { 7, 8, 9, 12, 13, 14, 17, 18, 19, 22, 23, 24 };
    self.bigDecorateIndex = centerPosList[math.random(1, #centerPosList)];
    self.hasBigDecorate = false;

    -- 根据地图区域获取风格
    local parentId;

    -- 如果在夹层迷宫中
    if UnrealDungeonM.isInUnreal() then
        local unrealId = UnrealDungeonM.getUnrealId();
        parentId = UnrealDungeonM.query(unrealId, "scene_style");
        style = DungeonAreaM.query(parentId, "style");
    else
        local dungeonId = DungeonM.getDungeonId();
        parentId  = DungeonAreaM.getParentId(dungeonId);
        style = DungeonAreaM.query(dungeonId, "style");
    end

    cc.SpriteFrameCache:getInstance():addSpriteFrames("images/cover/" .. style .. ".plist");

    -- 抽取格子的要素
    local function fetchGridIcon(totalAmount, needAmount)
        local tempList = { };

        if totalAmount >= needAmount then
            for i = 1, totalAmount do
                table.insert(tempList, i);
            end

            return randomSelectMulti(tempList, needAmount);
        else
            for i = 1, needAmount do
                local r = i % totalAmount;
                if r == 0 then
                    r = totalAmount;
                end

                table.insert(tempList, r);
            end

            return tempList;
        end
    end

    -- 抽取放置special格子的位置 可重复抽取
    local coverInfo = DungeonAreaM.queryCover(style);
    local totalSpecialNum = coverInfo.special;
    local specialNum = coverInfo.special_num;
    self.specialPosList = randomSelectMulti(centerPosList, specialNum, true);
    self.specialPosIconList = fetchGridIcon(totalSpecialNum, specialNum);

    -- 抽取放置special2格子的位置 不可重复抽取
    local totalSpecialNum2 = coverInfo.special2;
    local specialNum2 = coverInfo.special2_num;
    self.specialPosList2 = randomSelectMulti(centerPosList, specialNum2, false, self.specialPosList);
    self.specialPosIconList2 = fetchGridIcon(totalSpecialNum2, specialNum2);

    -- 预先随机小物件的位置, 每个区域 1 个
    self.smallDecorateIndexList = {};
    self.bigDecorateIndex = {};

    -- 外圈
    local quadrant1 = { 1, 2, 3, 4, 5, 6, 10, 11, 15, };
    local quadrant2 = { 16, 20, 21, 25, 26, 27, 28, 29, 30, };

    -- 内圈
    local quadrant3 = { 7, 8, 9, 12, 13, 14, };
    local quadrant4 = { 17, 18, 19, 22, 23, 24, };
    local allQuadrant = { quadrant1, quadrant2, quadrant3, quadrant4, };

    -- local smallDecorateNum = math.random(1, 4);
    if coverInfo.decorates_small > 0 then
        for i = 1, 4 do
            local quadrant = allQuadrant[i];

            -- 特殊地形，special格子上不放置小物件
            if coverInfo.no_decorate_on_special == false then
                for _, pos in pairs(self.specialPosList) do
                    local index = table.indexOf(quadrant, pos);
                    table.remove(quadrant, index);
                end

                for _, pos in pairs(self.specialPosList2) do
                    local index = table.indexOf(quadrant, pos);
                    table.remove(quadrant, index);
                end
            end

            -- 每个区域随机抽取 1 个
            local r = math.random(1, #quadrant);
            table.insert(self.smallDecorateIndexList, quadrant[r]);
        end
    end

    --下面这段是调试代码，为了方便在一个地牢中看到所有style的效果
    --暂时保留
    --local styles = table.keys(centers);
    --style = styles[(self.curFloor % #styles) + 1];

    -- 一些特殊的副本不需要装饰/特殊格子
    if FormulaM.invoke("CALC_NO_DECORATE_DUNGEON", DungeonM.getDungeonId()) then
        self.specialPosList = {};
        self.specialPosList2 = {};
        self.smallDecorateIndexList = {};
        self.bigDecorateIndex = {};
    end

    local gridWidth = GRID_WIDTH;
    local gridHeight = GRID_HEIGHT;
    local totalHeight = gridHeight * GRID_ROW_NUM;

    -- 设置顶部和底部背景
    -- 如果没有配置上下底图标，默认使用关卡编号
    local topBottomIcon = DungeonAreaM.query(parentId, "top_bottom_icon");
    if topBottomIcon == "" or topBottomIcon == nil then
        topBottomIcon = parentId;
    end

    local topBg = findChildByName(self.node, "CT1/top_bg");
    self.topBgPath = path .. "top/" .. topBottomIcon .. ".png";
    topBg:loadTexture(self.topBgPath);
    self.bottomBgPath = path .. "bottom/" .. topBottomIcon .. ".png";
    local bottomBg = findChildByName(self.node, "CT1/bottom_bg");
    bottomBg:loadTexture(self.bottomBgPath);

    -- 铺背景底板
    local batchNode = cc.Node:create();
    self.panel:addChild(batchNode);

    for i = 1, GRID_ROW_NUM do
        for j = 1, GRID_COL_NUM do
            local bg = ccui.ImageView:create();
            bg:loadTexture(self:getBgPath());
            bg:setTouchEnabled(true);

            -- 用力按背景地砖，可以自动拾取
            bg:addTouchEventListener(function (sender, eventType)
                local function forcePressCallback(forceGrade)
                    AutoFightM.startAutoPickup(forceGrade);
                end

                local function cancelCallback()
                    AutoFightM.stopAutoPickup();
                end

                -- 处理用力按压
                if handleForcePress(sender, eventType, forcePressCallback, cancelCallback) then
                    return;
                end
            end);

            local x = (j-1)*gridWidth + gridWidth / 2;
            local y = totalHeight - (i - 1) * gridHeight + GRID_OFFSET_Y + 5;
            bg:setPosition(x, y);
            batchNode:addChild(bg);
        end
    end

    -- 最上面一排无用地砖的阴影
    local shadow = ccui.ImageView:create();
    shadow:loadTexture("images/ui/common/brick_shadow.png");
    shadow:setScale9Enabled(true);
    shadow:setCapInsets(cc.rect(3, 8, 1, 2));
    shadow:setContentSize(642, 18);
    shadow:setPosition(320, totalHeight + 135);
    self.panel:addChild(shadow);

    -- 绘制场景
    self:generateScene();

    local specialType = FormulaM.invoke("IS_SPECIAL_COVER_PATH", DungeonM.getDungeonId());
    -- 生成格子
    local zOrder = 6;
    self.grids = {}
    for i = 1, GRID_ROW_NUM do
        for j = 1, GRID_COL_NUM do
            local index = (i - 1) * GRID_COL_NUM + j;
            local grid = UIGrid.create(levelData[index], index, self:getCoverPath(index, specialType), self:getDecoratePath(index));
            local x = (j-1)*gridWidth + gridWidth / 2;
            local y = totalHeight - (i - 1) * gridHeight + GRID_OFFSET_Y;
            local initZOrder = zOrder - j;
            grid.initZOrder = initZOrder;
            grid:setLocalZOrder(initZOrder);
            grid:setPosition(x, y);
            grid:setName("grid"..index);
            self.panel:addChild(grid);
            self.grids[index] = grid;

            if levelData[index].has_key == 1 then
                self.keyIndex = index;
            end

            if levelData[index].type == GRID_TYPE_DOOR then
                self.doorIndex = index;
            end
        end

        zOrder = zOrder + 5;
    end

    -- 如果不在角斗台中/特殊场景活动副本
    if not ArenaM.isInArena() and
        not ActivityDungeonM.isInActivityBossLayer() then
        -- 初始化格子周围
        self:decorateAround();
    elseif UnrealDungeonM.isInUnreal() then
        -- 夹层看看有没有配置
        local unrealId = UnrealDungeonM.getUnrealId();
        local dbase = UnrealDungeonM.query(unrealId, "dbase");
        if dbase["decorate_around"] == 1 then
            -- 需要初始化格子周围
            self:decorateAround();
        else
            self.panel.spaceChange = true;
        end
    else
        self.panel.spaceChange = true;
    end

    -- 记录一下levelData
    self.levelData = levelData;

    -- 初始化完毕后模拟门这个格子被打开的事件，使得周围的格子可点击
    if self.doorIndex then
        EventMgr.fire(event.OPEN_GRID, { ["type"] = GRID_TYPE_DOOR, ["pos"] = self.doorIndex, })
    end
end

-- 播放一次开格特效
function UIDungeonMain:autoOpenEffect(pos)
    local interval = 0.1;
    local index = 0;
    local grid = self.grids[pos];
    if grid.gridData.auto_open_effect
        and grid.gridData.state ~= GRID_STATE_DISABLE then

        local function delay()
            self:openGrid(grid.gridData:getPos(), grid.gridData.type, false);
            playEffect(grid, 1172);

            -- 抛出UI层开格子事件
            EventMgr.fire(event.UI_GRID_OPEN, { ["pos"] = pos, ["type"] = grid.type, ["isQuite"] = false});
        end
        performWithDelay(self, delay, interval * index + 2.0);
        index = index + 1;
    end
end

-- 抛出UI层格子事件
function UIDungeonMain:uiGridOpenEvent()
    -- 有一些格子可能已经开过了
    for _, grid in pairs(self.levelData) do
        if grid.state ~= GRID_STATE_COVER then
            -- 采用静默方式打开格子
            local pos = grid:getPos();
            if grid.auto_open_effect and grid.state ~= GRID_STATE_DISABLE
                and not DungeonM.hasContext() then
                -- 开格特效
                self:autoOpenEffect(grid:getPos());
            else
                self:openGrid(pos, grid.type, true);
                -- 抛出UI层开格子事件
                EventMgr.fire(event.UI_GRID_OPEN, { ["pos"] = pos, ["type"] = grid.type, ["isQuite"] = true});
            end
        end
    end
end

-- 绘制场景
function UIDungeonMain:generateScene()
    local sceneId;
    local dungeonId = DungeonM.getDungeonId();
    if UnrealDungeonM.isInUnreal() then
        -- 夹层迷宫
        local unrealId = UnrealDungeonM.getUnrealId();
        sceneId = UnrealDungeonM.query(unrealId, "scene_id");
    elseif DungeonInstanceM.isInInstance() then
        -- 副本
        local id = DungeonInstanceM.getCurrentInstanceId();
        local instanceInfo = DungeonInstanceM.getInstanceInfo(id);
        if type(instanceInfo["unreal_scene_id"]) == "number" then
            sceneId = instanceInfo["unreal_scene_id"];
        end
    elseif ActivityDungeonM.isInActivityBossLayer() then
        -- 特殊场景的活动副本
        local config = ActivityDungeonM.getActivityByDungeonId(dungeonId);
        if config then
            sceneId = config["boss_scene"];
            -- 可能需要替换场景
            -- sceneId = FormulaM.invoke("CALC_ACTIVITY_DUNGEON_BOSS_SCENE", sceneId);
        end
    elseif AwakeM.isAwakeDungeon(dungeonId) and
        DungeonAreaM.query(dungeonId, "dbase")["secen_id"] then
        sceneId = DungeonAreaM.query(dungeonId, "dbase")["secen_id"];
    end

    if type(sceneId) ~= 'number' then
        return;
    end

    -- 获取场景资源
    local resources = UnrealDungeonM.getResourceBySceneId(sceneId);
    if type(resources) ~= 'table' or #resources == 0 then
        -- 获取不到场景资源
        return;
    end

    local totalHeight = GRID_HEIGHT * GRID_ROW_NUM;

    for _, id in pairs(resources) do
        local resCfg = UnrealDungeonM.queryResource(id);
        local iconPath = getUnrealSceneIconPath(resCfg["icon"]);
        local effectId = resCfg["effect"];
        local gridPos = resCfg["grid_pos"];
        local scale = resCfg["scale"];
        local mod = gridPos % GRID_COL_NUM;
        local col = iif(mod == 0, GRID_COL_NUM, mod);
        local row = math.ceil(gridPos / GRID_COL_NUM);

        local x = (col - 1) * GRID_WIDTH + GRID_WIDTH / 2 + 1;
        local y = totalHeight - (row - 1) * GRID_HEIGHT + GRID_OFFSET_Y + 5;

        local resNode = cc.Node:create();
        resNode:setPosition(cc.p(x, y));

        if type(effectId) ~= "number" then
            -- 未配置光效，则显示图片
            local image = ccui.ImageView:create(iconPath);
            resNode:addChild(image);

            -- 设置图片锚点
            local anchor = resCfg["anchor"];
            if type(anchor) ~= "table" then
                anchor = cc.p(0.5, 0.5);
            else
                anchor = cc.p(tonumber(anchor[1]) or 0, tonumber(anchor[2]) or 0);
            end
            image:setAnchorPoint(anchor);

            -- 设置图片坐标
            local coords = resCfg["coords"];
            if type(coords) ~= "table" then
                coords = cc.p(0, 0);
            else
                coords = cc.p(tonumber(coords[1]) or 0, tonumber(coords[2]) or 0);
            end
            image:setPosition(coords);

            -- 设置水平翻转
            local isFlipped = resCfg["is_flipped_x"];
            if isFlipped == 1 then
                image:setFlippedX(true);
            end

            if type(scale) == "number" then
                image:setScale(scale);
            end
        else
            -- 配置了光效，则播放光效
            local effectCoords = resCfg["effect_coords"];
            playEffect(resNode, effectId, effectCoords[1], effectCoords[2]);
        end

        -- 如果需要覆盖格子，则层级要比所有的格子高
        local isCover = (resCfg.is_cover == 1);
        if isCover == true then
            resNode:setLocalZOrder(100);
        end

        self.panel:addChild(resNode);
    end
end

-- 获取周围格子的资源
function UIDungeonMain:getAroundCover(pos, specialType)
    local prefix = "images/cover/".. style .. "/";

    if type(specialType) == "string" then
        cc.SpriteFrameCache:getInstance():addSpriteFrames("images/cover/dracula.plist");
        if specialType == "chess" then
            -- 诸神棋盘(德古拉)
            prefix = "images/cover/".. "dracula" .. "/";
        end
    end

    local path = "";
    if pos == 0 then
        path = "around/" .. "right_bottom.png";
    elseif pos == 6 then
        path = "around/" .. "left_bottom.png";
    elseif pos == 49 then
        path = "around/" .. "right_top.png";
    elseif pos == 55 then
        path = "around/" .. "left_top.png";
    elseif pos <= 6 then
        path = "around/" .. "bottom.png";
    elseif pos >= 49 then
        path = "around/" .. "top.png";
    elseif (pos + 1) % (GRID_COL_NUM+2) == 0 then
        path = "around/" .. "left.png";
    elseif pos % (GRID_COL_NUM+2) == 0 then
        path = "around/" .. "right.png";
    end

    return prefix .. path;
end

-- 初始化格子周围， 缩小为90%，周围铺上一圈格子
function UIDungeonMain:decorateAround()
    local gridWidth = GRID_WIDTH;
    local gridHeight = GRID_HEIGHT;
    local totalRow = (GRID_ROW_NUM + 2);
    local totalCol = (GRID_COL_NUM + 2);
    local totalHeight = gridHeight * GRID_ROW_NUM;

    local blendColor = TextStyleM.TEXT_COLOR_BLEND;

    local bgList = {};

    -- 特殊迷宫
    local specialType = FormulaM.invoke("IS_SPECIAL_COVER_PATH", DungeonM.getDungeonId());

    -- 外围的格子
    for i = 0, GRID_ROW_NUM do
        for j = 0, GRID_COL_NUM + 1 do
            local index = i * (GRID_COL_NUM + 2) + j;
            if (i % totalRow) == 0                         -- 第一行
                or (i % totalRow) == GRID_ROW_NUM + 1      -- 最后一行
                or (j % totalCol) == 0                     -- 第一列
                or (j % totalCol) == GRID_COL_NUM + 1 then -- 最后一列

                local str = self:getAroundCover(index, specialType);
                local bg = ccui.ImageView:create();
                bg:loadTexture(str, 1);
                local x = (j-1)*gridWidth + gridWidth / 2;
                local y = totalHeight - (i - 1) * gridHeight + GRID_OFFSET_Y + 13;

                if (i % totalRow) == GRID_ROW_NUM + 1 then
                    bg:setLocalZOrder(100);
                else
                    bg:setLocalZOrder(-100 + i);
                end
                bg:setColor(blendColor);
                bg:setPosition(x, y);

                if bgList[str] == nil then
                    bgList[str] = {};
                end

                -- 同一种纹理归类，利用auto-batching功能
                table.insert(bgList[str], bg);
            end
        end
    end

    for _, list in pairs(bgList) do
        for _, bg in pairs(list) do
            self.panel:addChild(bg);
        end
    end

    self.panel:setScale(DUNGEON_AJUST_SCALE);
end

-- 获取英雄头像位置
function UIDungeonMain:getHeroPos()
    local modelNode = findChildByName(self.node, "CT2/model_node");
    local pos = modelNode:convertToWorldSpaceAR(cc.p(0, 0));

    local effectNode = findChildByName(self.node, "effect_node");
    local finalPos = effectNode:convertToNodeSpace(pos);

    return finalPos;
end

-- 获取英雄头像位置
function UIDungeonMain:getHeroPos2()
    local modelNode = findChildByName(self.node, "CT2/head_pic");
    local pos = modelNode:convertToWorldSpaceAR(cc.p(0, -5));

    local effectNode = findChildByName(self.node, "effect_node");
    local finalPos = effectNode:convertToNodeSpace(pos);

    return finalPos;
end

-- 获取怪物位置
function UIDungeonMain:getMonsterPos(pos)
    return self:getGridPos(pos);
end

-- 获取怪物位置
function UIDungeonMain:getMonsterPos2(pos)
    local grid = self.grids[pos];
    if not grid then
        cclog("格子(%s)不存在。", tostring(pos));
        return { ["x"] = 0, ["y"] = 0, };
    end
    return { ["x"] = grid:getPositionX(), ["y"] = grid:getPositionY(), };
end

-- 获取魔藤位置
function UIDungeonMain:getBinePos(pos)
    return self:getGridPos(pos);
end

-- 获取某个格子的坐标
function UIDungeonMain:getGridPos(pos)
    local grid = self.grids[pos];

    if not grid then
        cclog("格子(%s)不存在。", tostring(pos));
        return cc.p(0, 0);
    end

    return cc.p(grid:getPositionX(), grid:getPositionY());
end

-- 获取界面锁屏节点
function UIDungeonMain:getLockPanelParent()
    if self.lockPanelNode == nil then
        self.lockPanelNode = findChildByName(self.node, "lock_panel");
    end
    return self.lockPanelNode;
end

-- 获取特效父节点
function UIDungeonMain:getEffectParent()
    if self.effectNode == nil then
        self.effectNode = findChildByName(self.node, "effect_node");
    end
    return self.effectNode;
end

-- 获取发言特效父节点
function UIDungeonMain:getTalkEffectParent()
    if self.talkEffectNode == nil then
        self.talkEffectNode = findChildByName(self.node, "talk_node");
    end
    return self.talkEffectNode;
end

-- 获取BOSS特效父节点（裁剪内容请慎用）
function UIDungeonMain:getBossEffectParent()
    if self.bossEffectNode == nil then
        self.bossEffectNode = findChildByName(self.node, "boss_effect_node");
    end
    return self.bossEffectNode;
end

-- 获取特殊光效父节点
-- 该节点仅用于部分特殊光效（例如：石柱）请慎用
function UIDungeonMain:getSpecialEffectParent()
    if self.specialEffectNode == nil then
        self.specialEffectNode = findChildByName(self.node, "effect_ex_node");
    end
    return self.specialEffectNode;
end

-- 获取技能目标选择父节点
function UIDungeonMain:getSelectTargetParent()
    if self.selectTargetNode == nil then
        self.selectTargetNode = findChildByName(self.node, "select_target_node/panel");
    end
    return self.selectTargetNode;
end

-- 获取英雄头像节点
function UIDungeonMain:getHeroNode()
    if self.heroNode == nil then
        self.heroNode = findChildByName(self.node, "CT2/head_pic");
    end
    return self.heroNode;
end

-- 获取黑色蒙版节点
function UIDungeonMain:getBlackMask()
    if self.black_mask == nil then
        self.black_mask = findChildByName(self.node, "black_mask");
    end
    return self.black_mask;
end

-- 获取卷轴图标节点
function UIDungeonMain:getSpellNode()
    if self.spellNode == nil then
        self.spellNode = findChildByName(self.node, "CT2/juanzhou");
    end
    return self.spellNode;
end

-- 获取宝物图标节点
function UIDungeonMain:getEquipNode()
    if self.equipNode == nil then
        self.equipNode = findChildByName(self.node, "CT2/baowu");
    end
    return self.equipNode;
end

-- 获取血瓶图标节点
function UIDungeonMain:getHpIconNode()
    if self.hpIconNode == nil then
        self.hpIconNode = findChildByName(self.node, "CT2/hp_bg");
    end
    return self.hpIconNode;
end

-- 获取血瓶文本节点
function UIDungeonMain:getHpTextNode()
    if self.hpTextNode == nil then
        self.hpTextNode = findChildByName(self.node, "CT2/hp_val");
    end
    return self.hpTextNode;
end

-- 获取蓝瓶图标节点
function UIDungeonMain:getMpIconNode()
    if self.mpIconNode == nil then
        self.mpIconNode = findChildByName(self.node, "CT2/mp_bg");
    end
    return self.mpIconNode;
end

-- 获取蓝瓶文本节点
function UIDungeonMain:getMpTextNode()
    if self.mpLabel == nil then
        self.mpLabel = findChildByName(self.node, "CT2/mp_val");
    end
    return self.mpLabel;
end

-- 获取攻击图标节点
function UIDungeonMain:getAttackIconNode()
    if self.attackIconNode == nil then
        self.attackIconNode = findChildByName(self.node, "CT2/atk_bg");
    end
    return self.attackIconNode;
end

-- 获取攻击文本节点
function UIDungeonMain:getAttackTextNode()
    if self.attackTextNode == nil then
        self.attackTextNode = findChildByName(self.node, "CT2/atk_val");
    end
    return self.attackTextNode;
end

-- 获取魔力图标节点
function UIDungeonMain:getMagicIconNode()
    if self.magicIconNode == nil then
        self.magicIconNode = findChildByName(self.node, "CT2/magic_bg");
    end
    return self.magicIconNode;
end

-- 获取魔力文本节点
function UIDungeonMain:getMagicTextNode()
    if self.magicTextNode == nil then
        self.magicTextNode = findChildByName(self.node, "CT2/magic_val");
    end
    return self.magicTextNode;
end

-- 获取金币图标节点
function UIDungeonMain:getMoneyIconNode()
    if self.moneyIconNode == nil then
        self.moneyIconNode = findChildByName(self.node, "CT1/money_bg/icon");
    end
    return self.moneyIconNode;
end

-- 获取金币文本节点
function UIDungeonMain:getMoneyTextNode()
    if self.moneyTextNode == nil then
        self.moneyTextNode = findChildByName(self.node, "CT1/money_bg/num");
    end
    return self.moneyTextNode;
end

-- 获取特殊道具图标节点
function UIDungeonMain:getSpecialItemIconNode()
    if self.specialItemIconNode == nil then
        self.specialItemIconNode = findChildByName(self.node, "CT1/special_item_bg/icon");
    end
    return self.specialItemIconNode;
end

-- 获取特殊道具文本节点
function UIDungeonMain:getSpecialItemTextNode()
    if self.specialItemTextNode == nil then
        self.specialItemTextNode = findChildByName(self.node, "CT1/special_item_bg/num");
    end
    return self.specialItemTextNode;
end

-- 隐藏选择目标bg
function UIDungeonMain:hideSelectTargetBg()
    local node = findChildByName(self.node, "select_target_bg");
    node:setVisible(false);
    return node;
end

-- 显示选择目标bg
function UIDungeonMain:showSelectTargetBg()
    local node = findChildByName(self.node, "select_target_bg");
    node:setVisible(true);
    return node;
end

-- 获取已打开的怪物格子列表
function UIDungeonMain:getOpenMonsterGrids()
    for _, index in pairs(table.keys(self.openMonsterGrids)) do
        local grid = self.openMonsterGrids[index];
        -- 已经没怪了(转化成其他召唤怪或者死亡了？)
        if grid.gridData.state == GRID_STATE_DISABLE or
           grid.gridData.monster == nil or grid.gridData.monster:isDead() or
          (grid.gridData.monster.isAudience and grid.gridData.monster:isAudience()) then
            -- 移除掉
            self.openMonsterGrids[index] = nil;
        end
    end

    return table.values(self.openMonsterGrids);
end

-- 获取所有格子列表
function UIDungeonMain:getAllGrids()
    return self.grids;
end

-- 获取选择技能目标的蒙版父节点
function UIDungeonMain:getMaskParent()
    local maskNode = findChildByName(self.node, "CT1/mask");
    return maskNode;
end

-- 注册点击事件
function UIDungeonMain:registerTouchEvent()
    -- 卷轴按钮
    local btn_Magic = findChildByName(self.node, "CT2/juanzhou");
    local function onMagicOnClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("button_spell");

            if ME.user.forbidToOpenMagicUI and not DungeonGuideM.isGuideFinished() then
                -- 禁止打开魔法书界面
                return;
            end

            -- 打开魔法书界面
            self:showMagicScrollUI();
        end
    end
    AddTouchEventListener(btn_Magic, onMagicOnClick);

    -- 宝物按钮
    local btnTreasure = findChildByName(self.node, "CT2/baowu");
    local function onTreasureOnClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("button_bag");
            if UIMgr.getCurrentScene():isOpen("UIEquips") then
            else
                if equipsUI == nil then
                    equipsUI = UIEquips.create();
                    equipsUI:retain();
                    UIMgr.getCurrentScene():addForm(equipsUI);
                else
                    equipsUI:resetPos();
                    equipsUI:show();
                    UIMgr.getCurrentScene():addForm(equipsUI);
                end
            end
            AudioM.playFx("ui_open1");
        end
    end
    AddTouchEventListener(btnTreasure, onTreasureOnClick);

    -- 英雄格子
    local bgHero = findChildByName(self.node, "CT2/bg3");
    local function onBgHeroOnClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("slime_magician_app");
            if UIMgr.getCurrentScene():isOpen("UIHeroAttrib") then
                UIMgr.getCurrentScene():removeFormByName("UIHeroAttrib");
            end
            local uiHeroAttrib = UIHeroAttrib.create();
            UIMgr.getCurrentScene():addForm(uiHeroAttrib);
        end
    end
    AddTouchEventListener(bgHero, onBgHeroOnClick);

    -- 施法选择目标背景
    local screen_bg = self.node:getChildByName("select_target_bg")
    local function onOnClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            local form = UIMgr.getCurrentScene():getFormByName("UISelectTarget");
            if form then
                form:spellNoTarget();
            end
        end
    end
    screen_bg:addTouchEventListener(onOnClick);

    -- 称号冒泡点击事件
    local careerBubble = findChildByName(self.node, "CT2/career_bubble/bg");
    careerBubble:setTouchEnabled(true);
    local function onBubbleClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("slime_magician_app");
            -- 打开战斗属性界面
            UIMgr.getCurrentScene():removeFormByName("UIHeroAttrib");
            local uiHeroAttrib = UIHeroAttrib.create();
            UIMgr.getCurrentScene():addForm(uiHeroAttrib);
        end
    end
    careerBubble:addTouchEventListener(onBubbleClick);
end

-- 打开魔法书界面
function UIDungeonMain:showMagicScrollUI(dragon)
    if UIMgr.getCurrentScene():isOpen("UIMagicScrolls") then
    else
        if nil == magicScrollUI then
            magicScrollUI = UIMagicScrolls.create();
            magicScrollUI:retain();
        end
        magicScrollUI:resetPos();
        UIMgr.getCurrentScene():addForm(magicScrollUI);
    end
    if not DungeonGuideM.isGuideFinished() then
        magicScrollUI:resetIndex();
    end
    magicScrollUI:show(dragon);
end

-- 注册找到钥匙的处理函数
function UIDungeonMain:onOpenDoor()
    AudioM.playFx("pick_key");

    local ct1 = findChildByName(self.node, "CT1");
    if not ct1 then
        return;
    end

    -- 钥匙飞入
    local keyGrid = self.grids[self.keyIndex];
    local doorGrid = self.grids[self.doorIndex];

    local fromPosX = keyGrid:getPositionX();
    local fromPosY = keyGrid:getPositionY();
    local toPosX = doorGrid:getPositionX();
    local toPosY = doorGrid:getPositionY();

    local duration = math.max(cc.pGetDistance(cc.p(fromPosX, fromPosY), cc.p(toPosX, toPosY)), 400) / 800;

    local key = ccui.ImageView:create();
    key:loadTexture("images/ui/level/item/key.png");
    key:setPosition(fromPosX, fromPosY);

    local callfunc = cc.CallFunc:create(function()
        ct1:removeChild(key);

        --解开下一层入口
        self.grids[self.doorIndex]:openEntry();
    end);

    local moveTo = cc.EaseInOut:create(cc.MoveTo:create(duration, cc.p(toPosX, toPosY)), 2.0);
    local rotate = cc.RotateBy:create(duration, 360);
    key:runAction(cc.Sequence:create( cc.Spawn:create(moveTo,rotate), callfunc));
    ct1:addChild(key);
end

-- 激活有display字段的格子
function UIDungeonMain:activeDisplayGrid(pos, reActive, force)
    local uiGrid = self.grids[pos];
    local grid = DungeonM.getGridByPos(pos);

    -- 没有display字段
    if not force and not grid.display and not (grid.type == GRID_TYPE_STONE or grid.type == GRID_TYPE_KRYPTONITE) then
        return;
    end

    -- 如果已经激活过了
    if grid.activeState and not reActive then
        return;
    end

    grid.activeState = true;

    -- 尝试去"高亮"临格
    local adjoinGrids = DungeonM.getAdjoinGrids(pos);
    for i = 1, #adjoinGrids do
        local targetPos = adjoinGrids[i];
        local g = DungeonM.getGridByPos(targetPos);
        local ok = DungeonM.canOpenGrid(targetPos);
        if not g:isOpened() and ok ~= GRID_OPEN_DISABLE then
            -- 显示“亮”
            self.grids[targetPos]:gotoVisible();

            -- 被锁住了
            if ok == GRID_OPEN_LOCK then
                self.grids[targetPos]:gotoLocked();
            end
        end

        self:activeDisplayGrid(targetPos, false, force);
    end
end

function UIDungeonMain:openGrid(pos, type, isQuite)
    if not pos then
        return;
    end

    local grid = DungeonM.getGridByPos(pos);

    -- 打开格子
    self.grids[pos]:gotoOpen(isQuite);

    local function callback()
        Profiler.funcBegin("open_grid_callback");

        local dungeonId = DungeonM.getDungeonId();
        local layer     = DungeonM.currentLayer();

        -- 判断邻格是否开启
        if type ~= GRID_TYPE_BLOCK or DungeonBossM.isBossLayer(dungeonId, layer) then
            local adjoinGrids = DungeonM.getAdjoinGrids(pos);
            for i = 1, #adjoinGrids do
                local targetPos = adjoinGrids[i];
                local g = DungeonM.getGridByPos(targetPos);
                local ok = DungeonM.canOpenGrid(targetPos);
                if not g:isOpened() and ok ~= GRID_OPEN_DISABLE then
                    -- 显示“亮”
                    self.grids[targetPos]:gotoVisible();

                    -- 被锁住了
                    if ok == GRID_OPEN_LOCK then
                        self.grids[targetPos]:gotoLocked();
                    end
                end

                -- 尝试激活有display字段的格子
                if not grid.display and not grid:isPublicGrid() then
                    if g.type == GRID_TYPE_BLOCK
                        and DungeonM.query(tonumber(g.class), "rule") == "steam_block" then
                        self:activeDisplayGrid(targetPos, false, true);
                    else
                        self:activeDisplayGrid(targetPos);
                    end
                end
            end
        end

        if grid:isMonster() then
            -- 有些怪物出现时会增加场上其他怪物的攻击和生命，因此需要尝试刷新一下界面
            for _, monsterGrid in pairs(self.openMonsterGrids) do
                monsterGrid:updateHPAndAttack();
            end

            -- 保存到已打开的怪物格子列表中
            if table.indexOf(self.openMonsterGrids, self.grids[pos]) == -1 then
                table.insert(self.openMonsterGrids, self.grids[pos]);
            end

            -- 如果是怪物格子且围格未开，尝试加锁
            local grid = DungeonM.getGridByPos(pos);

            if not grid:isPublicGrid() and FormulaM.invoke("CAN_MONSTER_DEFENSE_GRID", grid) then
                local aroundGrids = DungeonM.getAroundGrids(pos);
                for i = 1, #aroundGrids do
                    local targetPos = aroundGrids[i];
                    local g = DungeonM.getGridByPos(targetPos);

                    if not g:isOpened() then
                        self.grids[targetPos]:gotoLocked();
                    end
                end
            end
        end
        Profiler.funcEnd("open_grid_callback");
    end

    callback();
end

-- 注册事件处理回调函数
function UIDungeonMain:registerEventCallback()
    -- 运行所有格子和出战英雄的动作
    local function playCommonActions()
        for _, grid in pairs(self.grids) do
            grid.actionManager:play();
        end

        self.heroActionManager:play();
    end

    -- 注册格子开启的处理函数
    EventMgr.register("UIDungeonMain", event.OPEN_GRID, function(params)
        local pos = params.pos;
        local type = params.type;
        local isQuite = params.isQuite or false;
        local isDelay = params.isDelay;

        -- 加入到缓存队列中
        self.cacheOpenGrids = self.cacheOpenGrids or {};
        local info = { ["pos"] = pos, ["type"] = type, ["isQuite"] = isQuite };
        table.insert(self.cacheOpenGrids, info);

        if isDelay then
            -- 如果需要延迟播放，先返回
            return;
        end

        -- 稍微延迟一下再开格子，避免卡顿
        performWithDelay(self, function()
            self:doCacheGridOpen();
            playCommonActions();
        end, 0.01);
    end);

    -- 自动销毁装备
    -- EventMgr.register("UIDungeonMain", event.SELF_DESTRUCT_EFFECT, function(classId)
    --     local tip = SelfDestructM.query(classId, "tip");
    --     if tip then
    --         alert(tip);
    --     end
    -- end);

    EventMgr.register("UIDungeonMain", event.GRID_LOCK_STATE, function(params)
        -- 如果是怪物格子且围格未开，尝试加锁
        local pos = params.pos;
        local grid = DungeonM.getGridByPos(pos);

        -- 如果格子还未开启，就不管了
        if not grid:isOpened() then
            return;
        end

        if grid:isMonster() and
            not grid:isPublicGrid() then
            local aroundGrids = DungeonM.getAroundGrids(pos);
            for i = 1, #aroundGrids do
                local targetPos = aroundGrids[i];
                local g = DungeonM.getGridByPos(targetPos);
                -- 未打开
                if not g:isOpened() then
                    if DungeonM.canOpenGrid(targetPos) == GRID_OPEN_LOCK then
                        self.grids[targetPos]:gotoLocked();
                    else
                        self.grids[targetPos]:gotoUnlocked();
                    end
                end
            end
        end
    end);

    -- 副本消失
    EventMgr.register("UIDungeonMain", event.INSTANCE_DISAPPER, function(para)
        local pos = para.pos;
        if pos and pos > 0 then
            self:activeDisplayGrid(pos, false, true);
        end
    end);

    -- 灯塔奖励
    EventMgr.register("UIDungeonMain", event.LIGHTHOUSE_BONUS, function(para)
        for pos = 1, GRID_SIZE do
            local grid = self.grids[pos];
            if GRID_TYPE_LIGHTHOUSE == grid.gridData.type  then
                grid:takeLighthouse();
            end
        end

        -- alert(getLocStr("lighthouse_tip"));
    end);

    -- 怪物更换模型
    EventMgr.register("UIDungeonMain", event.GRID_CHANGE_MODEL, function(params)
        local pos = params.pos;
        local changeClassId = params.changeClassId;
        local grid = DungeonM.getGridByPos(pos);

        -- 如果格子还未开启，就不管了
        if not grid:isOpened() then
            return;
        end

        if grid:isMonster() then
            local g = DungeonM.getGridByPos(pos);
            if g:isOpened() then
                -- 设置更换模型ID
                grid.monster.dbase:set("changeClassId", changeClassId);

                -- 更换模型
                self.grids[pos]:changeMonsterModel();

                -- 血量可能变化了，刷新一下
                if grid.type == GRID_TYPE_GHOST_SHIP then
                    self.grids[pos]:redrawShipAttackAndHp();
                else
                    self.grids[pos]:updateHPAndAttack();
                end
            end
        end
    end);

    -- 还原怪物模型
    EventMgr.register("UIDungeonMain", event.GRID_ORI_MODEL, function(params)
        local pos = params.pos;
        local grid = DungeonM.getGridByPos(pos);

        -- 如果格子还未开启，就不管了
        if not grid:isOpened() then
            return;
        end

        if grid:isMonster() or grid.type == GRID_TYPE_GHOST_SHIP then
            local g = DungeonM.getGridByPos(pos);
            if g:isOpened() then
                -- 清除更换模型ID
                grid.monster.dbase:delete("changeClassId");

                -- 还原模型
                self.grids[pos]:showOriMonsterModel();

                -- 血量可能变化了，刷新一下
                if grid.type == GRID_TYPE_GHOST_SHIP then
                    self.grids[pos]:redrawShipAttackAndHp();
                else
                    self.grids[pos]:updateHPAndAttack();
                end
            end
        end
    end);

    -- 怪物变身
    EventMgr.register("UIDungeonMain", event.MONSTER_TRANSFORM, function(params)
        local pos = params.pos;
        local classId = params.class_id;
        local grid = DungeonM.getGridByPos(pos);

        -- 如果格子还未开启，就不管了
        if not grid:isOpened() then
            return;
        end

        if grid:isMonster() then
            -- 有些怪物出现时会增加场上其他怪物的攻击和生命，因此需要尝试刷新一下界面
            for _, monsterGrid in pairs(self.openMonsterGrids) do
                monsterGrid:updateHPAndAttack();
            end

            -- 更换模型
            self.grids[pos]:monsterTransform(classId);

            -- 尝试发言
            self.grids[pos]:showAnimationTalk(MONSTER_ACTION_ATTACK);
        end
    end);

    -- 怪物停止动画
    EventMgr.register("UIDungeonMain", event.GRID_LOCK_ACTION, function(params)
        local pos = params.pos;
        local endPauseRound = params.endPauseRound;

        local function doFunc(pos)
            local uiGrid = self.grids[pos];
            local grid = DungeonM.getGridByPos(pos);

            -- 如果格子还未开启，就不管了
            if not grid:isOpened() then
                return;
            end

            -- 流浪者帐篷处理
            if uiGrid and uiGrid.gridData.type == GRID_TYPE_INSTANCE then
                local instanceId = uiGrid.gridData.instanceId;
                if DungeonTentM.isTentInstance(instanceId) then
                    uiGrid:showForzenCd();
                    uiGrid:setIsPause(true);
                end
                return;
            end

            if grid:isMonster() and grid:isOpened() then
                -- 已经打开，暂停动作
                self.grids[pos]:pauseMonsterAction(true);
            end
        end

        if type(pos) == "table" then
            for _, p in pairs(pos) do
                doFunc(p);
            end
        else
            doFunc(pos);
        end

    end);

    -- 怪物显示待机动画
    EventMgr.register("UIDungeonMain", event.GRID_PLAY_ACTION, function(params)
        local pos = params.pos;

        local function doFunc(uiGrid)
            -- 流浪者帐篷处理
            if uiGrid and uiGrid.gridData.type == GRID_TYPE_INSTANCE then
                local instanceId = uiGrid.gridData.instanceId;
                if DungeonTentM.isTentInstance(instanceId) then
                    uiGrid:showNormalCd();
                    uiGrid:setIsPause(false);
                end
                return;
            end

            -- 未翻开的格子也给处理，因为时间停止也会作用到未翻开的怪物
            -- 如果这时候时间停止过期了，怪物沙漏没有被移除
            uiGrid:playMonsterAction();
        end

        if type(pos) == "table" then
            for _, p in pairs(pos) do
                local uiGrid = self.grids[p];
                doFunc(uiGrid);
            end
        else
            local uiGrid = self.grids[pos];
            doFunc(uiGrid);
        end
    end);

    -- 怪物播放石化效果
    EventMgr.register("UIDungeonMain", event.GRID_PLAY_STONE, function(para)
        local pos    = para["pos"];
        local uiGrid = self.grids[pos];

        -- 石化
        petrifiedTarget(pos);
    end);

    -- 怪物停止石化效果
    EventMgr.register("UIDungeonMain", event.GRID_STOP_STONE, function(para)
        local pos    = para["pos"];
        local uiGrid = self.grids[pos];

        stopPetrified(pos);
    end);

    -- 怪物技能失效
    EventMgr.register("UIDungeonMain", event.INVALID_SKILL, function(para)
        local skillId = para["skill_id"];
        local monster = para["monster"];
        local pos     = monster:getPos();
        local grid    = DungeonM.getGridByPos(pos);

        -- 播放技能失效特效
        if grid:isOpened() and grid:isMonster() and not monster:isDead() then
            self.grids[pos]:playInvalidSkillEffect(skillId);
        end
    end);

    -- boss光效播完之后
    EventMgr.register("UIDungeonMain", event.BOSS_SHOW, function(para)
        local bossGrid = para["grid"];
        if bossGrid.gridData.type == GRID_TYPE_BOSS then
            performWithDelay(self, function ()
                bossGrid:switchMonsterAction(MONSTER_ACTION_APPEAR);

                local function talk()
                    bossGrid:showAnimationTalk(MONSTER_ACTION_APPEAR);
                end
                performWithDelay(self, talk, 0.5);
            end, 0.3);
        end
    end);

    -- 注册捡取物品的处理函数
    EventMgr.register("UIDungeonMain", event.PICK_UP_ITEM, function(params)
        self.grids[params.pos]:onPickUp(params.bonus, params.newBonus,
            params.isItemVisible, params.noAlert, nil, params.borrowGrid);
        local pos = params.pos;
        local type = params.type;

        -- 如果是拾取地图
        if type == GRID_TYPE_MAP then
            -- 判断邻格是否开启
            local adjoinGrids = DungeonM.getAdjoinGrids(pos);
            for i = 1, #adjoinGrids do
                local targetPos = adjoinGrids[i];
                local ok = DungeonM.canOpenGrid(targetPos);
                if ok == GRID_OPEN_OK then
                    self.grids[targetPos]:gotoVisible();
                end
            end
        end

        self:whenPickUpItem(params);
        -- 更新界面UI
        --self:updateUI();
    end);

    -- 关注玩家离开夹层迷宫的事件
    EventMgr.register("UIDungeonMain", event.LEAVE_UNREAL_DUNGEON, function(args)
        local leaveTalk = args.leaveTalk;
        if leaveTalk then
            startDialogueTemplate(leaveTalk);
        end
    end);

    -- 关注怪物死亡的事件
    EventMgr.register("UIDungeonMain", event.MONSTER_DIE_LOGIC, function(args)
        self:whenMonsterDie(args);
    end);

    -- 注册开格子获得金币的处理函数
    EventMgr.register("UIDungeonMain", event.OPEN_GRID_MONEY, function(params)
        -- 先记录，推迟到UI_GRID_OPEN事件再播放动画
        self.playingPickUpMoney = self.playingPickUpMoney or {};

        self.playingPickUpMoney[params.pos] = params;
    end);

    -- 注册技能获得龙珠的处理函数
    EventMgr.register("UIDungeonMain", event.PROP_GAIN_DRAGON_BALL, function(params)
        -- 播放龙珠飞入转盘效果
        local grid = self.grids[params.pos];
        local itemId = params.bonus[2];
        local fileName = ItemM.query(itemId, "icon");
        local iconPath = getItemIconPath(fileName);
        if DragonWishM.isWishItem(itemId) then
            AudioM.playFx("pick_goods");

            -- 根据玩家已经有多少个龙珠，来选择图标
            -- 这个时候已经获得龙珠了
            local wishItemCount = DragonWishM.getWishItemCount();

            if wishItemCount > 7 then
                wishItemCount = 7;
            end
            local wishItemIcons = { 1500, 1501, 1502, 1503, 1504, 1505, 1506, };
            iconPath = getItemIconPath(wishItemIcons[wishItemCount]);

            pickDragonBallEffect(grid, UIDungeonMgr.getCurLevel().dungeonTopMenu:getDragonBallNode(), iconPath);
        end
    end);

    -- 注册执行祭坛操作的处理函数
    EventMgr.register("UIDungeonMain", event.DO_ALTAR_EVENT, function(params)
        self.grids[params.pos]:onDoAltarEvent();

        -- 更新界面UI
        self:updateUI();
    end);

    -- 注册占领矿脉的处理函数
    EventMgr.register("UIDungeonMain", event.OCCUPY_MINE, function(params)
        self.grids[params.pos]:onOccupyMine();

        -- 提示占领成功
        alert(getLocStr("occupy_mine_succ"));

        -- 更新界面UI
        -- self:updateUI();
    end);

    -- 关注领取矿脉奖励的事件
    EventMgr.register("UIDungeonMain", event.MINE_BONUS_TAKEN, function(params)
        -- 弹出提示
        self:hintMineBonus(params);
    end);

    -- 关注领取喷泉奖励的事件
    EventMgr.register("UIDungeonMain", event.TAKE_FOUNTAIN_BONUS, function(params)
        self.grids[params.pos]:onTakeFountainBonus(params.bonus);
    end);

    -- 关注领取地牢副本奖励的事件
    EventMgr.register("UIDungeonMain", event.TAKE_INSTANCE_BONUS, function(params)
        local bonus = params.bonus
        self.grids[params.pos]:onTakeInstanceBonus(bonus);
    end);

    -- 关注蜂巢消失的事件
    EventMgr.register("UIDungeonMain", event.BEEHIVE_DISAPPEAR, function(params)
        self.grids[params.pos]:onBeehiveDisappear();

        local grid = self.grids[params.pos];
        local function dropItem()
            -- 如果有掉落
            if grid.gridData.bonus then
                grid:initItem();
                grid:itemAppear();
            end
        end

        performWithDelay(self, dropItem, 2);
    end);

    -- 关注蜂巢伤害事件
    EventMgr.register("UIDungeonMain", event.BEEHIVE_DAMAGE, function(params)
        local sourcePos = params.pos;
        local targetPos = params.targetPos;

        -- 播放蜂巢特效
        local function beehiveAction()
            local sourceNode = self.grids[sourcePos];
            local duration = 0;
            if not targetPos or targetPos <= 0 then
                duration = playBehiveEffect(sourceNode, nil, self:getHeroPos());
            else
                local targetNode = self.grids[targetPos];

                trace("UIDungeonMain", "蜂巢格子：%d, 目标怪物格子：%d", params.pos, targetPos);
                duration = playBehiveEffect(sourceNode, targetNode, self:getHeroPos());
            end

            return duration;
        end

        if not targetPos or targetPos <= 0 then
            -- 加入到英雄的动作播放器
            self.heroActionManager:addAction(nil, beehiveAction, "beehiveAction", {}, DUNGEON_ACTION_PHASE_MONSTER_CLEAR);
        else
            -- 加入到目标怪物的动作播放器
            local monsterGrid = self.grids[targetPos];
            monsterGrid.actionManager:addAction(monsterGrid.gridData, beehiveAction, "beehiveAction", {}, GRID_ACTION_PHASE_MONSTER_DAMAGED);
        end

        EventMgr.fire(event.PLAYER_MOVE, { ["combatRound"] = true, });
    end);

    -- 关注发动陷阱的事件
    EventMgr.register("UIDungeonMain", event.LAUNCH_TRAP, function(params)
        self:whenLaunchTrap(params);

        local grid = self.grids[params.pos]
        grid:onLanchTrap();

        local function dropItem()
            -- 如果有掉落
            if grid.gridData.bonus then
                grid:initItem();
                grid:itemAppear();
            end
        end

        performWithDelay(self, dropItem, 2);
    end);

    -- 消除特性
  --[[  EventMgr.register("UIDungeonMain", event.CLEAR_FEATURE, function(params)
        local pos = params.pos;

        -- 尝试周围格子变成高亮
        local adjoinGrids = DungeonM.getAdjoinGrids(pos);
        for i = 1, #adjoinGrids do
            local targetPos = adjoinGrids[i];
            local g = DungeonM.getGridByPos(targetPos);
            local ok = DungeonM.canOpenGrid(targetPos);
            local grids = UIDungeonMgr.getCurLevel():getAllGrids();
            if not g:isOpened() and ok ~= GRID_OPEN_DISABLE then
                -- 显示“亮”
                grids[targetPos]:gotoVisible();

                -- 被锁住了
                if ok == GRID_OPEN_LOCK then
                    grids[targetPos]:gotoLocked();
                end

                local grid = DungeonM.getGridByPos(pos);
                -- 尝试激活有display字段的格子
                if not grid.display and not grid:isPublicGrid() then
                    UIDungeonMgr.getCurLevel():activeDisplayGrid(targetPos);
                end
            end
        end
    end); ]]

    -- 格子状态变化
    EventMgr.register("UIDungeonMain", event.CHANGE_GRID_STATE, function(params)
        local pos = params.pos;
        local state = params.state;
        local grid = self.grids[pos];
        if grid == nil then
            return;
        end

        if grid.gridData.type == GRID_TYPE_TRAP and state == GRID_STATE_DISABLE then
            local trapId = grid.gridData.id;
            local trapType = CommonTrapM.query(trapId, "type");
            local trapAlias = CommonTrapM.query(trapId, "alias");

            -- 如果是排炮
            if trapType == TRAP_TYPE_CANNON then
                -- 播放拆炮动画
                grid.animation:play(24);
                AudioM.playFx("demolish");
            elseif trapAlias == "magic_bow" then
                -- 破魔重弩
                grid:onTrapDisappear(2);
            else
                -- 陷阱消失
                grid:onTrapDisappear();
            end
        elseif state == GRID_STATE_DISABLE then
            -- 格子消失？
            grid:onGridDisappear();
        end

        if state == GRID_STATE_DISABLE then
            -- 激活一下周围的
            self:activeDisplayGrid(pos);
        end
    end);

    -- 矿山枯竭了
    EventMgr.register("UIDungeonMain", event.MINE_HILL_BONE_DRY, function(params)
        local pos = params.pos;

        self.grids[pos]:onMineHillDisappear();
    end);

    -- 采矿事件
    EventMgr.register("UIDungeonMain", event.MINING, function(params)
        local pos = params["pos"];

        self.grids[pos]:onMining();
    end);

    -- 关注采撷果树的事件
    EventMgr.register("UIDungeonMain", event.TAKE_FRUIT, function(params)
        local bonus = params.bonusInfo;
        self.grids[params.pos]:onTakeFruit(bonus);
    end);

    -- 怪物掉落事件
    EventMgr.register("UIDungeonMain", event.MONSTER_DROP_PROPERTY, function(params)
        local grid = self.grids[params.pos];
        local function doAppear()
            grid:initItem();
            grid.item:setVisible(true);
            -- 道具出现
            grid:itemAppear();
            return 0.01;
        end

        grid.actionManager:addAction(grid.gridData, doAppear, "doAppear", {}, GRID_ACTION_PHASE_ITEM_APPEAR);
    end);

    -- 关注领取冒险者残骸奖励的事件
    EventMgr.register("UIDungeonMain", event.TAKE_WRECKAGE_BONUS, function(params)
        self.grids[params.pos]:onTakeWreckage(params.bonus);
    end);

    -- 打败飞艇事件
    EventMgr.register("UIDungeonMain", event.DEFEAT_AIRSHIP, function(para)
        local grid = self.grids[para.pos + 1];
        grid:redrawSteamMachine();
    end);

    -- 幽灵船升级事件
    EventMgr.register("UIDungeonMain", event.SHIP_LEVEL_UP, function(params)
        self.grids[params.pos]:redrawShipAttackAndHp();
    end);

    -- 关注埋葬水手残骸的事件
    EventMgr.register("UIDungeonMain", event.BURY_SAILOR, function(pos)
        self.grids[pos]:onBurySailor(pos);
    end);

    -- 第三只手发动
    EventMgr.register("UIDungeonMain", event.THIRD_HAND_PLUNDER, function(params)
        alert(getLocStr("third_hand_plunder"));
    end);

    -- 帐篷升级第三只手
    EventMgr.register("UIDungeonMain", event.TENT_UPGRADE_THIRD_HAND, function(params)
        local pos     = params.pos;
        local levelUp = params.levelUp;

        if not levelUp then
            -- 没升级，没有效果
            return;
        end

        local classId = FormulaM.invoke("THIRD_HAND_CLASS_ID");
        local name = ItemM.query(classId, "name");

        local function okFunc()
            -- 飞入效果
            local uiDungeonMain = UIMgr.getCurrentScene():getFormByName("UIDungeonMain");
            if not uiDungeonMain then
                return;
            end
            local grids = uiDungeonMain:getAllGrids();
            if not grids or not grids[pos] then
                return;
            end
            grids[pos]:onPickUp({1, classId, 1}, nil, true, true, 1, true);
        end

        local function delay()
            -- 弹出奖励提示
            showBonusNotifyForm(name, {{1, classId, 1},}, okFunc,
                getLocStr("third_hand_bonus_tip"), getLocStr("btn_text_ok"));
        end

        -- 绑在uiMainMap上
        local uiMainMap = UIMgr.getCurrentScene().mainMap;
        if uiMainMap then
            -- 之所以做个延迟是因为事件抛出的时候，帐篷外的UIDungeonMain还未创建完
            performWithDelay(uiMainMap, delay, 1.5);
        else
            delay();
        end
    end);

    -- 关注特殊装备升级的事件
    EventMgr.register("UIDungeonMain", event.UPGRADE_SPECIAL_EQUIP, function(params)
        if not DungeonInstanceM.isInInstance() then
            -- 不是在副本中就不播放了（目前只有第三只手，先这么处理）
            return;
        end

        -- 不是第三只手
        local classId = params.classId;
        if classId ~= FormulaM.invoke("THIRD_HAND_CLASS_ID") then
            return;
        end

        -- 默认从中间飞
        local pos = 13;

        -- 飞入效果
        self.grids[pos]:onPickUp({1, classId, 1}, nil, true, true, 1, true);

        local function updateDelay()
            -- 更新界面UI
            self:updateUIByDesc("hp");
            self:updateUIByDesc("mp");
            self:updateUIByDesc("attack");
            self:updateUIByDesc("magic");
            self.noSyncProps = false;
        end

        local ct2 = findChildByName(self.node, "CT2");
        -- 暂时不记录属性数据
        self.noSyncProps = true;
        if ct2:isVisible() then
            -- 当前可以见立即更新
            updateDelay();
        else
            performWithDelay(self, updateDelay, 0.1);
        end
    end);

    -- 关注装备升级的事件
    EventMgr.register("UIDungeonMain", event.UPGRADE_EQUIP, function(params)
        local pos = params.pos;
        if type(pos) == 'number' then
            self.grids[pos]:onUpgradeEquip();
        else
            local classId = params.classId;
            local name = ItemM.query(classId, "name");
            local msg = string.format(getLocStr("equip_ugpraded_tip"), name);

            if ArenaM.isInArena() then
                -- 如果在角斗台中，需要延时提示（等胜利光效播完后再提示）
                performWithDelay(self, function() alert(msg); end, 3);
            elseif params.delay then
                -- 需要延时提示的
                performWithDelay(self, function() alert(msg); end, params.delay);
            else
                alert(msg);
            end
        end

        local function updateDelay()
            -- 更新界面UI
            self:updateUIByDesc("hp");
            self:updateUIByDesc("mp");
            self:updateUIByDesc("attack");
            self:updateUIByDesc("magic");
            self.noSyncProps = false;
        end

        local ct2 = findChildByName(self.node, "CT2");
        -- 暂时不记录属性数据
        self.noSyncProps = true;
        if ct2:isVisible() then
            -- 当前可以见立即更新
            updateDelay();
        else
            performWithDelay(self, updateDelay, 0.1);
        end
    end);

    -- 关注装备升级的事件(附魔师的铁砧)
    EventMgr.register("UIDungeonMain", event.RISKER_UPGRADE_EQUIP, function(params)

        local classId = params.classId;
        local name = ItemM.query(classId, "name");
        local msg = string.format(getLocStr("equip_ugpraded_tip"), name);
        alert(msg);

        local function updateDelay()
            -- 更新界面UI
            self:updateUIByDesc("hp");
            self:updateUIByDesc("mp");
            self:updateUIByDesc("attack");
            self:updateUIByDesc("magic");
            self.noSyncProps = false;
        end

        local ct2 = findChildByName(self.node, "CT2");

        -- 暂时不记录属性数据
        self.noSyncProps = true;
        if ct2:isVisible() then
            -- 当前可以见立即更新
            updateDelay();
        else
            performWithDelay(self, updateDelay, 0.1);
        end
    end);

    -- 注册银行兑换成功的处理函数
    EventMgr.register("UIDungeonMain", event.BANK_EXCHANGE_DONE, function(params)
        -- 更新界面UI
        self:updateUIByDesc("search_point");
    end);

    -- 注册购买地牢商品的处理函数
    EventMgr.register("UIDungeonMain", event.BUY_DUNGEON_GOODS, function(params)
        local shopType = params.shopType;
        -- 更新界面UI
        self:updateUIByDesc("search_point");

        if string.find(shopType, "black_market") then
            -- 黑市摊贩
            local value = DungeonServiceM.queryDungeonAttrib("black_market_feel");
            if value < 100 then
                alert(getLocStr("black_market_shop_tip"));
            elseif value == 100 then
                local title = getLocStr("black_market_shop_title");
                local msg = getLocStr("black_market_shop_extra_bonus");
                local okText = getLocStr("btn_text_leave");
                confirm2(CONFIRM_TYPE_OK, title, msg, nil, nil, okText);
            end
        end
    end)

    -- 标记怪物
    EventMgr.register("UIDungeonMain", event.MARK_MONSTER, function(params)
        local pos = params.pos;
        local grid = self.grids[pos];

        grid:showMonsterMark(true, params.fileName);
    end);

    -- 注册玩家分解卷轴的回调
    EventMgr.register("UIDungeonMain", event.SPLIT_SPELL, function()
        self:updateUIByDesc("mp");
    end);

    -- 注册玩家受到伤害的回调
    EventMgr.register("UIDungeonMain", event.HERO_DAMAGED, function(params)
        local delayHpEffect = ME.user.dbase:queryTemp("delayHpEffect");
        if delayHpEffect then
            return;
        end

        -- 如果需要延迟播放受创，那么延迟一下
        local delay = params["needDelay"];
        if type(delay) == "number" then
            performWithDelay(self.node, function()
                self:playHeroDamaged(params);
            end, delay);
        else
            -- 玩家受创
            self:playHeroDamaged(params);
        end
    end);

    -- 注册玩家复活的回调
    EventMgr.register("UIDungeonMain", event.HERO_REVIVE, function()
        -- 切换至待机状态
        self:switchHeroAction(HERO_ACTION_IDLE);

        -- 更新界面UI
        self:updateUI()
        self:checkHpAlert();
    end);

    -- 注册领取遗产的回调
    EventMgr.register("UIDungeonMain", event.TAKE_LEGACY, function(params)
        local legacy = params.legacy;
        local pos    = params.pos;

        self.grids[pos]:onTakeLegacy(legacy);

        -- 更新界面UI
        --self:updateUI();
    end);

    -- 注册格子解毒回调
    EventMgr.register("UIDungeonMain", event.GRID_PURGE, function(poses)
        if #poses <= 0 then
            return;
        end
        for _, pos in pairs(poses) do
            self.grids[pos]:gotoPurge();
        end
    end);

    -- 注册怪物死亡的处理函数
    EventMgr.register("UIDungeonMain", event.MONSTER_DIE, function(params)
        local pos = params.pos;
        local grid = self.grids[params.pos];
        local skillId = params.skillId;
        local source = params.source;

        local function doMonsterDie()
            -- 判断当前地牢数据是否合法
            if not DungeonM.getCurrentDungeon() then
                return;
            end

            -- 如果是怪物格子且围格未开，尝试解锁
            local aroundGrids = DungeonM.getAroundGrids(pos);
            for i = 1, #aroundGrids do
                local targetPos = aroundGrids[i];
                if DungeonM.canOpenGrid(targetPos) ~= GRID_OPEN_LOCK then
                    self.grids[targetPos]:gotoUnlocked();
                end
            end

            for targetPos = 1, GRID_SIZE do
                -- 判断是否被污染
                local logicGrid = DungeonM.getGridByPos(targetPos);
                if logicGrid.polluted and not logicGrid:isOpened() then
                    self.grids[targetPos]:gotoPolluted(logicGrid.polluted[5]);
                end
            end

            -- 判断邻格是否开启
            local adjoinGrids = DungeonM.getAdjoinGrids(pos);
            for i = 1, #adjoinGrids do
                local targetPos = adjoinGrids[i];
                local ok = DungeonM.canOpenGrid(targetPos);
                if ok ~= GRID_OPEN_DISABLE then
                    self.grids[targetPos]:gotoVisible();

                    -- 被锁住了
                    if ok == GRID_OPEN_LOCK then
                        self.grids[targetPos]:gotoLocked();
                    end
                end
            end

            -- 需要刷新下所有怪物的攻击血量
            local monster = grid.gridData.monster;
            if monster then
                for _, g in pairs(self.grids) do
                    g:updateHPAndAttack();
                end
            end

            -- 从已打开的怪物格子列表中删除
            -- 如果这个时候又有新的怪物在这个格子上了，就不删除
            if not monster or monster:isDead() then
                table.removeItem(self.openMonsterGrids, self.grids[pos]);
            end

            -- 物理攻击才处理吸能术效果
            if skillId and skillId < 0 and source == ME.user then
                -- 处理吸能术效果
                self:handleAbsorbEffect(grid);
            end

            return 0.5;
         end

        grid.actionManager:addAction(grid.gridData, doMonsterDie, "doMonsterDie", {}, GRID_ACTION_PHASE_MONSTER_DIED);
    end);

    -- 注册召唤兽死亡的处理函数
    EventMgr.register("UIDungeonMain", event.SUMMON_DIE, function(params)
        -- 从已打开的怪物格子列表中删除
        local summon = params.summon;
        local pos = summon:getPos();
        table.removeItem(self.openMonsterGrids, self.grids[pos]);

        local function doSummonDie()
            self.grids[pos]:onSummonDie();
            return 0.3;
        end

        local grid = self.grids[pos];
        if SummonM.isTotem(self.grids[pos].gridData) then
            return;
        end

        grid.actionManager:addAction(grid.gridData, doSummonDie, "doSummonDie", {}, GRID_ACTION_PHASE_MONSTER_DIED);

        -- 死亡发言
        local dieTip = SummonM.query(summon.classId, "die_tip");
        if type(dieTip) == 'string' and string.len(dieTip) > 0 then
            performWithDelay(grid, function() alert(dieTip); end, 0.5);
        end

        local dbase = SummonM.query(summon.classId, "dbase");
        if dbase["die_talk"] then
            startDialogueTemplate(dbase["die_talk"]);
        end
    end);

    -- 注册召唤兽攻击的处理函数
    EventMgr.register("UIDungeonMain", event.SUMMON_ATTACK, function(params)
        local summon = params.summon;
        local pos = summon:getPos();
        local grid = self.grids[pos];

        local function doSummonAttack()
            -- 播放攻击动作
            grid:switchSummonAction(SUMMON_ACTION_ATTACK);
            return 0.5;
        end

        grid.actionManager:addAction(grid.gridData, doSummonAttack, "doSummonAttack", {}, GRID_ACTION_PHASE_MONSTER_DAMAGED);

        -- TODO
        EventMgr.fire(event.PLAYER_MOVE, {["not_move"] = true, });
    end);

    -- 注册召唤兽出现的回调
    EventMgr.register("UIDungeonMain", event.CALL_SUMMON, function(args)
        -- 刷新状态条
        self.dungeonStatusBar:updateBar();
    end);

    -- 注册怪物受创的回调
    EventMgr.register("UIDungeonMain", event.MONSTER_DAMAGED, function(params)
        local grid = self.grids[params.pos];
        local damage = params.damage;
        local skillId = params.skillId;
        local needDelay = params.needDelay;
        local extra = params.extra or {};
        local countered = extra.countered or false;
        local source = params.source;

        local function demageAction()
            if countered then
                grid:switchMonsterAction(MONSTER_ACTION_STRIKE_BACK);
            else
                local extra = {};
                if grid.gridData and grid.gridData.monster then
                    extra = {["isPause"] = grid.gridData.monster.dbase:query("isPause")};
                end
                grid:switchMonsterAction(MONSTER_ACTION_DAMAGED, extra);
            end
            grid:updateHPAndAttack();
            grid:showHpDamaged(damage);

            -- 尝试受创动作发言
            grid:showAnimationTalk(MONSTER_ACTION_DAMAGED, skillId);
            return 0.1;
        end

        -- 播放受创动作
        if needDelay then
            local delayTime = iif(type(needDelay) == "number", needDelay, 0.5);
            performWithDelay(self.node, demageAction, delayTime);
        else
            grid.actionManager:addAction(grid.gridData, demageAction, "demageAction", {}, GRID_ACTION_PHASE_MONSTER_UPDATE);
        end

        if skillId and skillId < 0 and source == ME.user then
            -- 处理吸能术效果
            self:handleAbsorbEffect(grid);
        end
    end);

    -- 注册BOSS受到buff影响的回调
    EventMgr.register("UIDungeonMain", event.BOSS_RECIEVE_BUFF, function(params)
        local pos     = params["pos"];
        local skillId = params["skillId"];
        local grid = self.grids[pos];

        if not grid or not grid.gridData.monster then
            return;
        end

        local classId;
        local monsterDisplay;
        classId = grid.gridData.monster:query("class_id");
        monsterDisplay = MonsterDisplayM.getMonsterSkillDisplay(classId, skillId);

        -- 发言行为
        local function talkAnimation()
            local delay = 0;
            local talkInfo = AnimationTalkM.getActionSkillTalk(classId, MONSTER_ACTION_USER_DEFINED, skillId);

            -- 初始化发言随机数
            DungeonBossM.setTalkRandom();

            -- 尝试攻击动作发言
            grid:showAnimationTalk(MONSTER_ACTION_ATTACK, skillId);

            if talkInfo and type(num) == "number" and talkInfo.ratio then
                if num < talkInfo.ratio then
                    if talkInfo and #table.keys(talkInfo) > 0 then
                        delay = 0.5;
                    end
                end
            end

            return delay;
        end

        -- 如果配置了BOSS特效播放，那么就到UIBossEffect界面去播放
        if monsterDisplay and monsterDisplay.boss_effect == 1 then
            -- 如果有配置了技能发言，并且BOSS能够发言，那么延迟个0.5秒让玩家看清对话再进特效播放界面
            local delay = talkAnimation();

            -- 锁屏下，免得玩家继续操作
            self:lockScreen(2);

            performWithDelay(self, function ()
                local monsterNode = grid.monsterNode;

                if UIMgr.getCurrentScene():isOpen("UIBossEffect") then
                    -- 判断优先级
                    if (monsterNode.animationPriority or 0) > monsterDisplay.animation_priority then
                        return;
                    end

                    UIMgr.getCurrentScene():removeFormByName("UIBossEffect");
                end

                monsterNode.animationPriority = monsterDisplay.animation_priority;

                -- 打开特效播放界面
                local uiform = UIBossEffect.create(MONSTER_ACTION_USER_DEFINED, params);
                UIMgr.getCurrentScene():addForm(uiform);
            end, delay)
        end
    end);

    -- 注册BOSS受到debuf影响的回调
    EventMgr.register("UIDungeonMain", event.BOSS_RECIEVE_DEBUF, function(params)
        local pos     = params["pos"];
        local skillId = params["skillId"];
        local grid = self.grids[pos];
        if not grid or not grid.gridData.monster then
            return;
        end

        local classId;
        local monsterDisplay;
        classId = grid.gridData.monster:query("class_id");
        monsterDisplay = MonsterDisplayM.getMonsterSkillDisplay(classId, skillId);

        -- 发言行为
        local function talkAnimation()
            local delay = 0;
            local talkInfo = AnimationTalkM.getActionSkillTalk(classId, MONSTER_ACTION_USER_DEFINED, skillId);

            -- 初始化发言随机数
            DungeonBossM.setTalkRandom();

            -- 尝试攻击动作发言
            grid:showAnimationTalk(MONSTER_ACTION_ATTACK, skillId);

            if talkInfo and type(num) == "number" and talkInfo.ratio then
                if num < talkInfo.ratio then
                    if talkInfo and #table.keys(talkInfo) > 0 then
                        delay = 0.5;
                    end
                end
            end

            return delay;
        end

        -- 如果配置了BOSS特效播放，那么就到UIBossEffect界面去播放
        if monsterDisplay and monsterDisplay.boss_effect == 1 then
            -- 如果有配置了技能发言，并且BOSS能够发言，那么延迟个0.5秒让玩家看清对话再进特效播放界面
            local delay = talkAnimation();

            -- 锁屏下，免得玩家继续操作
            self:lockScreen(2);

            performWithDelay(self, function ()
                local monsterNode = grid.monsterNode;

                if UIMgr.getCurrentScene():isOpen("UIBossEffect") then
                    -- 判断优先级
                    if (monsterNode.animationPriority or 0) > monsterDisplay.animation_priority then
                        return;
                    end

                    UIMgr.getCurrentScene():removeFormByName("UIBossEffect");
                end

                monsterNode.animationPriority = monsterDisplay.animation_priority;

                -- 打开特效播放界面
                local uiform = UIBossEffect.create(MONSTER_ACTION_USER_DEFINED, params);
                UIMgr.getCurrentScene():addForm(uiform);
            end, delay);
        elseif monsterDisplay and sizeof(monsterDisplay) > 0 then
            -- 播放攻击动作
            grid:switchMonsterAction(MONSTER_ACTION_DAMAGED);

            -- 播放怪物攻击光效
            playMonsterDamageEffect(grid, grid.gridData.monster.classId, skillId);
        else
            -- 通用特效
            -- 锁下屏幕
            self:lockScreen(2);

            -- 尝试发言
            local delay = talkAnimation();

            local frame = MonsterM.query(classId, "damaged_frame");
            local speed = MonsterM.query(classId, "damaged_speed");

            -- 切换受创动作前几帧
            local display = grid.monsterNode.monsterDisplay;
            if display then
                local damagedFrame = {frame[1], frame[1] + 5}
                display:switchAction(MONSTER_ACTION_USER_DEFINED, {["frame"] = damagedFrame, ["speed"] = speed});
                -- 低语之森BOSS专用音效播放
                if classId == 505 and skillId == "default" then
                    performWithDelay(self, function() AudioM.playFx("protector_debuff"); end, 0.2);
                end
            end

            -- 通用特效
            performWithDelay(self, function()
                if UIMgr.getCurrentScene():isOpen("UIBossEffect") then
                    display:switchAction(MONSTER_ACTION_IDLE);
                    return;
                end

                local function callback()
                    frame = {frame[1] + 6, frame[2]}
                    display:switchAction(MONSTER_ACTION_USER_DEFINED, {["frame"] = frame, ["speed"] = speed, ["goOn"] = true,});
                end
                bossRecieveDebuf(grid.node, callback);
            end, 0.2);
        end
    end);

    -- 注册远程怪攻击回调
    EventMgr.register("UIDungeonMain", event.REMOTE_ATTACK, function(params)
        local pos     = params.pos;
        local skillId = params.skillId;
        local grid = self.grids[pos];

        if not grid or not grid.gridData.monster then
            return;
        end

        -- 如果是BOSS，则打开UIBossEffect界面，在该界面播放怪物动画
        local classId;
        local monsterDisplay;
        classId = grid.gridData.monster:query("class_id");
        monsterDisplay = MonsterDisplayM.getMonsterSkillDisplay(classId, skillId);

        if grid.gridData.monster.isAudience and grid.gridData.monster:isAudience() then
            -- 可能有光效
            local dbase = NormalAudienceM.queryAudience(classId, "dbase") or {};
            local effectId = dbase["remote_effect_id"];
            if effectId then
                local offset = dbase["remote_effect_offset"] or {0, 0};
                playEffect(grid, effectId, offset[1], offset[2]);
            end
        end

        -- 如果配置了BOSS特效播放，那么就到UIBossEffect界面去播放
        if monsterDisplay and monsterDisplay.boss_effect == 1 then
            -- 如果有配置了技能发言，并且BOSS能够发言，那么延迟个0.5秒让玩家看清对话再进特效播放界面
            local delay = 0;
            local talkInfo = AnimationTalkM.getActionSkillTalk(classId, MONSTER_ACTION_ATTACK, skillId);


            if talkInfo and talkInfo.ratio then
                if DungeonBossM.canBossTalk(talkInfo.ratio) then
                    delay = 1.3;
                end
            end

            -- 锁屏下，免得玩家继续操作
            wait(monsterDisplay.lock_screen);

            performWithDelay(self, function ()
                local monsterNode = grid.monsterNode;

                if UIMgr.getCurrentScene():isOpen("UIBossEffect") then
                    -- 判断优先级
                    if (monsterNode.animationPriority or 0) > monsterDisplay.animation_priority then
                        return;
                    end

                    UIMgr.getCurrentScene():removeFormByName("UIBossEffect");
                end

                monsterNode.animationPriority = monsterDisplay.animation_priority;

                -- 打开特效播放界面
                local uiTreasure = UIBossEffect.create(MONSTER_ACTION_ATTACK, params);
                UIMgr.getCurrentScene():addForm(uiTreasure);
            end, delay)
        else
            -- 播放攻击动作
            grid:switchMonsterAction(MONSTER_ACTION_ATTACK, { ["skillId"] = skillId, });

            -- 播放怪物攻击光效
            playMonsterAttackEffect(grid, grid.gridData.monster.classId, skillId);
        end
    end);

    -- 注册怪物受创的回调
    EventMgr.register("UIDungeonMain", event.GHOST_SHIP_DAMAGED, function(params)
        local grid = self.grids[params.pos];
        local damage = params.damage;

        -- 播放受创动作
        grid:switchMonsterAction(MONSTER_ACTION_DAMAGED);
        grid:updateHPAndAttack();
        grid:showHpDamaged(damage);
    end);

    -- 注册召唤兽受创的回调
    EventMgr.register("UIDungeonMain", event.SUMMON_DAMAGED, function(params)
        local grid = self.grids[params.pos];
        local damage = params.damage;

        -- 播放受创动作
        grid:switchSummonAction(SUMMON_ACTION_DAMAGED);
        grid:updateSummonHPAndAttack();
        grid:showHpDamaged(damage);
    end);

    -- 注册召唤兽状态恢复
    EventMgr.register("UIDungeonMain", event.SUMMON_RECOVER, function(pos)
        local grid = self.grids[pos];

        -- 刷新状态
        grid:updateSummonHPAndAttack();
    end);

    -- 注册实验台升级的回调
    EventMgr.register("UIDungeonMain", event.LAB_UPGRADE_EQUIP, function(params)
        local arr = SummonM.findSummons();

        for _, g in pairs(arr) do
            local grid = self.grids[g:getPos()];

            -- 更新下召唤兽属性
            grid:updateSummonHPAndAttack();
        end
    end);

    -- 怪物属性（ui）刷新
    EventMgr.register("UIDungeonMain", event.REFRESH_MONSTER_INFO, function(params)
        for pos = 1, GRID_SIZE do
            local grid = DungeonM.getGridByPos(pos);

            -- 如果是已经翻开的未死亡的怪物
            if grid:isOpened() and grid.monster and not grid.monster:isDead() then
                self.grids[pos]:updateHPAndAttack();
            end
        end
    end);

    -- 刷新怪物信息
    EventMgr.register("UIDungeonMain", event.MONSTER_INFO_UPDATED, function(params)
        local pos = params.pos;

        if not self.grids or not self.grids[pos] then
            return;
        end

        local grid = self.grids[pos];

        -- 更新血量和攻击
        grid:updateHPAndAttack();
    end);

    -- 战斗状态
    EventMgr.register("UIDungeonMain", event.APPLY_COMBAT_STATUS, function(params)
        local pos = params.pos;

        if not self.grids or not self.grids[pos] then
            return;
        end

        local grid = self.grids[pos];

        -- 更新血量和攻击
        grid:updateHPAndAttack();

        -- 状态光效
        local status = params.status;
        playCombatStatusEffect(grid, status);
    end);

    -- 状态清除
    EventMgr.register("UIDungeonMain", event.COMBAT_STATUS_PROP_CLEAR, function(params)
        local target = params.target;

        if target.type ~= OBJECT_TYPE_MONSTER then
            return;
        end

        local pos = target:getPos();
        local grid = self.grids[pos];
        local status = params.status;

        removeCombatStatusEffect(grid, status);
    end);

    -- 迷宫坍塌
    EventMgr.register("UIDungeonMain", event.DUNGEON_COLLAPSE, function(para)
        local left_round = tonumber(para.left);

        if left_round <= 0 then
            self:dungeonCollapse();
        elseif left_round <= 10 then
            alert(string.format(getLocStr("dungeon_collapse_warnning"), left_round));
        end
    end);

    -- 关注回合事件
    EventMgr.register("UIDungeonMain", event.COMBAT_ROUND, function(params)
        -- 每回合更新上次GC时间, 玩家长久不操作的情况下，GCM会定时进行lua垃圾回收
        GCM.updateActiveTime();

        -- 检查BUFF状态
        performWithDelay(self, function() self.dungeonStatusBar:updateBar(); end, 0.01);

        -- 检查时间停止效果
        self:checkTimeCeaseEffect();

        local isDelay = false;

        if type(params) == 'table' then
            if params.isDelay == true then
                isDelay = true;
            elseif type(params.bonus) == "table" and #params.bonus > 0 then
                -- 如下属性比较特殊，需要等动画播完后在更新界面
                local specialList = { "max_hp", "max_mp", "attack", "magic", "search_point" };
                if type(params.bonus[1]) == "table" then
                    for _, arr in pairs(params.bonus) do
                        if table.indexOf(specialList, arr[2]) then
                            isDelay = true;
                        end
                    end
                else

                end
            end
        end

        if isDelay == true then
            self:updateCD();
            return;
        end

        performWithDelay(self, function() self:whenCombatRound(); end, 0.1);
    end);

    -- 每回合结束
    EventMgr.register("UIDungeonMain", event.END_ROUND, function(round)
        self:updateCD();
    end);

    -- 准备就绪
    EventMgr.register("UIDungeonMain", event.DUNGEON_AREADY, function(params)

    end);

    -- 注册怪物复活的回调
    EventMgr.register("UIDungeonMain", event.MONSTER_REVIVE, function(params)
        local pos = params.pos;
        local grid = self.grids[pos];
        local monster = grid.gridData.monster;
        if not monster then
            return;
        end

        local classId = monster.classId;

        -- 播放复活光效
        local effectId = MonsterM.query(classId, "revive_effect");
        playEffect(grid, effectId);

        grid:updateHPAndAttack();
    end);

    -- 关注召唤兽消失事件
    EventMgr.register("UIDungeonMain", event.SUMMON_DISAPPEAR, function(params)
        -- 如果不是图腾，暂时不用管了
        if params.totem ~= 1 then
            return;
        end

        local classId = params["classId"];
        local pos = params.pos;
        local grid = self.grids[pos];
        table.removeItem(self.openMonsterGrids, grid);

        -- 图腾
        local function doSummonDie()
            if SummonM.isTotem(grid.gridData) then
                grid:onTotemDisappear();
            end
            grid:onSummonDie(classId);
            return 1.5;
        end
        grid.actionManager:addAction(grid.gridData, doSummonDie, "doSummonDie", {}, GRID_ACTION_PHASE_MONSTER_DIED);

        -- 遍历所有的已经翻开怪物格子
        for pos = 1, GRID_SIZE do
            local grid = self.grids[pos];
            local monster = grid.gridData.monster;

            if grid.gridData:isOpened() and monster and not monster:isDead() then
                grid:updateHPAndAttack();
            end
        end
    end);

    -- 检查是否怪物全部清除
    local function checkMonster()
        if ME.user:getHp() <= 0 then
            return;
        end

        -- 判断怪物是否全部清除
        if self.cleared then
            return;
        end

        -- 更新一把所有怪物的血量
        local ok = true;
        for i = 1, TOTAL_GRID_NUM do
            if not self.grids[i]:isCleared() then
                ok = false;

                -- 如果是怪物
                if self.grids[i].gridData:isMonster() then
                    self.grids[i]:updateHPAndAttack();
                end
            end
        end

        -- 还没清怪
        if not ok then
            return;
        end

        self.cleared = true;
        DungeonServiceM.monsterClear();
    end

    -- 动作播放
    local damageAction = function(pos)
        local sequenceList = SkillM.getSequenceList();
        for target, sequence in pairs(sequenceList) do
            local callbackList = sequence:getCommonActionCallbackList();
            local i;

            if #callbackList > 0 then
            end

            local targetGrid = self.grids[target:getPos()];
            if targetGrid ~= nil then
                local phase = GRID_ACTION_PHASE_MONSTER;
                for i = 1, #callbackList do
                    -- 将怪物Sequence中的动作依次加入到格子中的actionManager里面
                    targetGrid.actionManager:addAction(targetGrid.gridData, callbackList[i], "sequence", {}, phase + i);
                end
            elseif target == ME.user then
                local phase = GRID_ACTION_PHASE_HERO;
                -- 出战英雄
                for i = 1, #callbackList do
                    -- 将英雄Sequence中的动作依次加入到heorActionManager里面
                    self.heroActionManager:addAction(nil, callbackList[i], "sequence", {}, phase + i);
                end
            end
        end
    end

    -- 注册一个动作完成后的回调
    EventMgr.register("UIDungeonMain", event.ONE_COMMON_ACTION_DONE, function()
        -- 每个动作播放完毕后，尝试播放迷宫的动作
        self:tryPlayDungeonCommonActions();
    end);

    -- 注册剩余污染回合数的回调
    EventMgr.register("UIDungeonMain", event.POLLUTE_LEFT_ROUND, function(param)
        local pos = param["pos"];
        local leftRound = param["left_round"];

        self.grids[pos]:updatePollutedLeftRound(leftRound, param["pollute_type"]);
    end);

    -- 注册boss死亡的回调
    EventMgr.register("UIDungeonMain", event.BOSS_DIE, function(param)
        if UnrealDungeonM.isInUnreal() then
            -- 如果在夹层中，检查传送门显示
            local unrealId = UnrealDungeonM.getUnrealId();
            local dbase = UnrealDungeonM.query(unrealId, "dbase");
            local doorGrid = self.grids[self.doorIndex];
            if doorGrid.item:isVisible() == false and dbase["door_appear_by_clear"] == 1 then
                local function doorAppear()
                    local exitNode = findChildByName(self.node, "CT1/cave_mask/exit_node");
                    playBuildingAppearEffect(nil, exitNode);
                    local exitImg = findChildByName(exitNode, "exit");
                    portalEffect(exitImg, 60, 66);
                end
                performWithDelay(self, doorAppear, 3);
            elseif dbase["brave_vs_dragon"] == 1 and DungeonBossM.isBossLayer(dungeonId, layer, ME.user) then
                -- 如果是勇者斗恶龙的BOSS夹层，直接打开门
                local doorGrid = self.grids[self.doorIndex];
                doorGrid:openEntry(true);
            end
        elseif ActivityDungeonM.isInActivityBossLayer() then
            -- 活动副本，boss死亡门直接显示
            local doorGrid = self.grids[self.doorIndex];
            if not doorGrid.canEnterNextLevel then
                doorGrid:openEntry(true);
            end
            return;
        end

        -- 如果还未清怪
        if not DungeonServiceM.isMonsterClear() then
            return;
        end

        -- 移除格子上的监狱图标
        for _, grid in pairs(self.grids) do
            grid:onAllMonsterKilled();
        end
    end);

    -- 注册怪物全部清除的回调
    EventMgr.register("UIDungeonMain", event.MONSTER_CLEAR, function(param)
        -- 如果在BOSS层，那么直接响应事件
        -- 如果是帐篷界面不响应
        if DungeonTentM.isTentInstance(self.instanceId) and not DungeonBossM.isInBossLayer() then
            return;
        end

        if ActivityDungeonM.isInActivityBossLayer() then
            -- 活动副本，怪物全部死了门直接显示
            local doorGrid = self.grids[self.doorIndex];
            if not doorGrid.canEnterNextLevel then
                doorGrid:openEntry(true);
            end
        end

        -- 移除格子上的监狱图标
        for _,grid in pairs(self.grids) do
            grid:onAllMonsterKilled();
        end

        -- 提示玩家关卡已清除
        local gainSP = param["search_point"];

        -- 怪物全部清除处理
        self:monsterClearFunc(gainSP);
    end);

    -- 幽灵船攻击落空（没有目标）
    EventMgr.register("UIDungeonMain", event.SHIP_ATTACK_NULL, function(args)
        local grid = self.grids[args.pos];
        grid:updateCountDown();
    end);

    -- 注册actionmanager延迟事件
    EventMgr.register("UIDungeonMain", event.COMMON_ATCION_DELAY, function(args)
        local pos = args.pos; -- -1表示迷宫动作播放列表  0 表示英雄播放列表 其他表示格子播放列表
        local delay = args.delay;
        local actionManager;
        if pos == -1 then
            actionManager = self.dungeonActionManager;
        elseif pos == 0 then
            actionManager = self.heroActionManager;
        else
            local gridNode = self.grids[pos];
            actionManager = gridNode.actionManager;
        end

        local function doDelay()
            return delay;
        end

        actionManager:addAction(self.gridData, doDelay, "doDelay", {}, GRID_ACTION_PHASE_GRID_OPENED);
    end);

    -- 注册玩家出手的处理函数
    EventMgr.register("UIDungeonMain", event.PLAYER_MOVE, function(args)
        if self.playMoveNode == nil then
            self.playMoveNode = cc.Node:create();
            self:addChild(self.playMoveNode);
        end

        local function doPlayerMove()
            -- 尝试执行缓存的开格子事件
            local interval;
            local combatRound;
            local extra = {};
            if type(args) == 'table' then
                interval = args.interval;
                combatRound = args.combatRound;
                extra["delayInfo"] = args.delayInfo;
            end

            self:doCacheGridOpen(interval, extra);

            damageAction();

            if combatRound == true then
                -- 尝试执行一下回合事件的回调
                -- 注：当回合事件(event.COMBAT_ROUND)到达时，可能指定了延迟播放
                -- 例如：蜂巢、陷阱这类的建筑需要等攻击动画播完后，再更怪物血量、倒计时等
                self:whenCombatRound();
            end

            -- 运行ActionManger
            playCommonActions();

        end

        self.playMoveNode:stopAllActions();
        performWithDelay(self.playMoveNode, doPlayerMove, 0.01);
    end);

    -- 注册玩家使用卷轴的处理函数
    EventMgr.register("UIDungeonMain", event.USE_SPELL, function(params)
        -- 获取技能ID
        local skillId = params.skillId;

        self:whenApplyMagic(skillId);
    end);

    -- 注册玩家使用冈布奥专属技能的处理函数
    EventMgr.register("UIDungeonMain", event.USE_SLIME_SKILL, function(params)
        -- 获取技能ID
        local skillId = params.skillId;

        self:whenApplyMagic(skillId);
    end);

    -- 注册门打开的处理函数
    EventMgr.register("UIDungeonMain", event.OPEN_DOOR, function(pos)
        self.grids[pos]:onPickUp();
        self:onOpenDoor();
    end);

    -- 注册玩家信息更新的回调
    EventMgr.register("UIDungeonMain", event.USER_INFO_UPDATED, function(desc)
        -- TODO 下面这些情形需要等动画播完后再刷新界面，暂时没有好的方法，只能枚举列出
        local specialList = {
            "pick_item",        -- 拾取
            "drift_bottle",     -- 漂流瓶
            "skummer",          -- 走私者残骸
            "head_altar",       -- 头骨祭坛
            "statue_bonus",     -- 雕像
            "fruit",            -- 果树
            "rubbish_bonus",    -- 垃圾堆
            "winner_stone",     -- 胜利者石柱
            "villager_bonus",   -- 村民奖励
            "treasure_bonus",   -- 宝藏奖励
            "fountain_bonus",   -- 喷泉
            "dungeon_instance", -- 副本奖励
            "element_altar_bonus", -- 元素圣坛
            "sailor_wreckage",  -- 水手残骸奖励
            "legacy",           -- 遗产
            "apply_property",   -- 属性道具
            "tarot_bonus",      -- 塔罗牌奖励
            "pixy_well",        -- 妖精之井
            "riddle_sphinx",    -- 狮身人面像
        };

        if desc then
            --print("desc:"..desc)
        end

        if table.indexOf(specialList, desc) == -1 then
            self:updateUIByDesc(desc);
            self:checkHpAlert();
        end

        if desc == "search_point" then
            self:checkCareerAnima();
        end
    end);

    -- 注册特效播放完毕的回调
    EventMgr.register("UIDungeonMain", event.UI_EFFECT_DONE, function(args)
        local desc = args.desc;

        if not desc then
            -- 没有描述，默认刷新全部
            self:updateUI();
            self:checkHpAlert();
            return;
        end

        self:updateUIByDesc(desc);

        -- 可能还有其他序列需要播放，例如：获得属性的同时受创了
        --EventMgr.fire(event.PLAYER_MOVE, -1);
    end);

    -- 注册需要刷新界面的回调
    EventMgr.register("UIDungeonMain", event.NEED_REFRESH_ATTRIB, function(args)
        local desc = args.desc;

        if not desc then
            -- 没有描述，默认刷新全部
            self:updateUI();
            self:checkHpAlert();
            return;
        end

        self:updateUIByDesc(desc);
    end);

    -- 注册神龙许愿触发的回调
    EventMgr.register("UIDungeonMain", event.WISH_TRIGGER, function(params)
        if params.isOpenForm ~= true then
            -- 没通知开启界面，不处理
            return;
        end

        -- 通过称号获得龙珠时，需要先显示奖励窗口，再显示许愿界面
        local desc = params.desc;
        local options = params.options;

        -- 先禁止操作，防止出现SLIMEC-2823中的情况
        self:lockScreen(2);

        -- 转盘上的龙珠已经满了，立即显示许愿界面
        if not UIDungeonMgr.getCurLevel().dungeonTopMenu:getDragonBallNode() then
            UIMgr.getCurrentScene():removeFormByName("UIDragonWish");
            local uiDragonWish = UIDragonWish.create(options);
            UIMgr.getCurrentScene():addForm(uiDragonWish);
        end

        -- 先将操作缓存起来，待龙珠飞入转盘后播放
        self.cacheWishOptions = options;
    end);

    -- 关注玩家穿上装备
    EventMgr.register("UIDungeonMain", event.EQUIP, function(classId)
        local function delay()
            local function update()
                self:updateHPAndAttack();
                self:updateUI();
            end

            local ct2 = findChildByName(self.node, "CT2");
            if ct2:isVisible() then
                -- 节点可见，立即更新
                update();
            else
                -- 延迟更新
                performWithDelay(self, update, 0.5);
            end
        end
        performWithDelay(self, delay, 1);

        local arr = SummonM.findSummons();

        for _, g in pairs(arr) do
            local grid = self.grids[g:getPos()];

            -- 更新下召唤兽属性
            grid:updateSummonHPAndAttack();
        end

        for _, grid in pairs(self.grids) do
            grid:onEquip(classId);
        end

        -- 如果有魔藤，刷新其攻击和血量显示
        self:redrawBineAttackAndHp();

        -- 刷新状态条
        self.dungeonStatusBar:updateBar();
    end);

    -- 关注玩家卸下装备
    EventMgr.register("UIDungeonMain", event.UNEQUIP, function(params)
        local classId = params.classId;

        self:updateHPAndAttack();
        self:updateUI();

        -- 刷新下召唤兽信息
        local arr = SummonM.findSummons();
        for _, g in pairs(arr) do
            local grid = self.grids[g:getPos()];

            -- 更新下召唤兽属性
            grid:updateSummonHPAndAttack();
        end

        for _, grid in pairs(self.grids) do
            grid:onUnEquip(classId);
        end

        -- 如果有魔藤，刷新其攻击和血量显示
        self:redrawBineAttackAndHp();

        -- 刷新状态条
        self.dungeonStatusBar:updateBar();
    end);

    -- 开始下一层消息
    EventMgr.register("UIDungeonMain", event.START_NEXT_FLOOR, function()
        -- 移除所有事件回调
        EventMgr.removeAll("UIDungeonMain");
    end);

    -- 关注玩家进入下一层的消息
    EventMgr.register("UIDungeonMain", event.TRY_ENTER_NEXT_FLOOR, function()
        local function doEnter()

            -- 增加游戏统计
            GameAnalytics.finishLevel("level_"..DungeonM.getDungeonId().."_"..DungeonM.currentLayer());

            -- 显示正在进入下一层
            if not DungeonM.isMaxLayer() then
                showLoadingForm();

                -- 播放音效
                AudioM.playFx("downstairs");
            end

            local result = DungeonActionM.go("next_floor");

            hideLoadingForm();
            if true == result then
                AudioM.playFx("button_click");

                return;
            end

            if type(result) == "string" then
                alert(result, false);
            end
        end

        -- 暂时禁止自动拾取功能
        do
            doEnter();
            return;
        end

        -- 拾取可捡取物品
        local hasPickUp = false;
        for i = 1, TOTAL_GRID_NUM do
            if true == DungeonActionM.go("pick_item", self.grids[i].index) then
                hasPickUp = true;
            end
        end

        if hasPickUp then
            performWithDelay(self, doEnter,  1);
        else
            doEnter();
        end
    end);

    -- 关注登录“在地牢中”消息
    EventMgr.register("UIDungeonMain", event.MESSAGE_DONE, function(para)
        if para[1] == "MSG_END_SYNC" and para[2].result == 0 then
            stillOpt = false;
            self.dungeonTopMenu:setNotInFight();
        end
    end);

    -- 关注停止自动战斗消息 STOP_AUTO_FIGHT
    EventMgr.register("UIDungeonMain", event.STOP_AUTO_FIGHT, function(params)
        stillOpt = false;
    end);

    -- 注册HP吸血恢复通知
    EventMgr.register("UIDungeonMain", event.HP_VAMPIRE, function(hp)
        local node = findChildByName(self.node, "CT3/hp_add_tip");
        local function callfunc()
            if DungeonM.canRecoverAttrib(ME.user, "hp") == true then
                playAttribUpEffect(node, hp, cc.p(0, 50), "max_hp");
            end
        end

        performWithDelay(node, callfunc, 0.8);
    end);

    -- 注册MP吸血恢复通知
    EventMgr.register("UIDungeonMain", event.MP_VAMPIRE, function(mp)
        local node = findChildByName(self.node, "CT3/hp_add_tip");
        if DungeonM.canRecoverAttrib(ME.user, "mp") == true then
            playAttribUpEffect(node, mp, cc.p(0, 50), "max_mp");
        end
    end);

    -- 注册HP恢复通知
    EventMgr.register("UIDungeonMain", event.HP_RECOVER, function(hp)
        local node = findChildByName(self.node, "CT3/hp_add_tip");
        if DungeonM.canRecoverAttrib(ME.user, "hp") == true then
            playAttribUpEffect(node, hp, cc.p(0, 50), "max_hp");
        end
    end);

    -- 龙珠许愿完成
    EventMgr.register("UIDungeonMain", event.WISH_DONE, function(args)
        -- 先同步下数量
        self.dungeonTopMenu:syncBallCount();

        -- 刷新界面
        self.dungeonTopMenu:updateUI();
    end);

    -- 奖励事件
    EventMgr.register("UIDungeonMain", event.DO_BONUS, function(bonus)
        local itemId = bonus[2];
        local function delay()
            self:autoEquip(itemId);
        end
        -- TODO: 这里丑陋了点，但是暂时无解，因为PropertyM中是先奖励再addAction的
        -- 如果这里不延时则会导致先do_equip再use_property，服务器会异常的
        if bonus[1] ==  1 then
            performWithDelay(self, delay, 0);
        end

        -- 添加奖励的classId至新道具列表
        if bonus[1] == 1 and
            (PropertyM.isProperty(bonus[2]) or
            EquipM.isEquipment(bonus[2])) then

            ItemM.updateNewItemList(bonus[2]);
        end

        if bonus[1] == 2 and bonus[2] == "search_point" then
            -- 如果获得了探索点，检查是否播放学习称号动画
            self:checkCareerAnima();
            return;
        elseif bonus[1] == 2 and bonus[2] == "hp" or bonus[2] == "limit_hp" then
            -- 如果获得了hp。检查是否还需要血量预警
            self:checkHpAlert();
            return;
        elseif bonus[1] == 2 and bonus[2] == "ship_level" then
            -- 增加幽灵船等级
            local ship = ME.user.ship;
            if ship and not DungeonInstanceM.isInInstance() then
                self.grids[ship:getPos()]:redrawShipAttackAndHp();
            end
        end
    end);

    --  技能奖励事件
    EventMgr.register("UIDungeonMain", event.SKILL_BONUS, function(args)
        self:whenSelfBonus(args["bonus"]);
    end);

    -- 物品奖励事件
    EventMgr.register("UIDungeonMain", event.BONUS_ITEM, function(args)
        local itemId = args.classId;
        local amount = args.amount;
        local pos =  args.pos;

        -- 播放飞入包裹的动画
        if type(pos) == "number" then
            local grid = self.grids[pos];
            local bonus = { 1, itemId, amount };
            grid:onPickUp(bonus, nil, true);
        end
    end);

    -- 日常任务完成事件
    EventMgr.register("UIDungeonMain", event.DAILY_TASK_FINISH, function(args)
        if args ~= nil and #args > 0 then
            playEquipGainEffectOnEnter(self:getEquipNode(), self:getHeroPos(), args[2], args[3]);
        end
    end);

    -- 注册学习职业成功的事件回调
    EventMgr.register("UIDungeonMain", event.JOIN_CAREER, function(args)
        self:checkCareerAnima();
        local function delay()
            self:updateHPAndAttack();

            -- 刷新下召唤兽的属性
            local pos;
            for pos = 1, GRID_SIZE do
                local grid = DungeonM.getGridByPos(pos);

                -- 如果是已经翻开的未死亡的怪物
                if grid:isOpened() and grid.summon and not grid.summon:isDead() then
                    self.grids[pos]:updateSummonHPAndAttack();
                end
            end

            -- 如果有魔藤，刷新其攻击和血量显示
            self:redrawBineAttackAndHp();
        end
        performWithDelay(self, delay, 1);
    end);

    -- 注册升级职业成功的事件回调
    EventMgr.register("UIDungeonMain", event.UPGRADE_CAREER, function(args)
        self:checkCareerAnima();
        local function delay()
            self:updateHPAndAttack();
        end
        performWithDelay(self, delay, 1);
    end);

    -- 注册下需要刷新怪物信息事件immediate
    EventMgr.register("UIDungeonMain", event.IMMEDIATE_UPDATE_MONSTER, function(args)
        self:updateHPAndAttack();
    end);

    -- 注册怪物逃跑事件回调
    EventMgr.register("UIDungeonMain", event.MONSTER_FLEE, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];

        grid:onMonsterFlee();
    end);

    -- 怪物转换成建筑
    EventMgr.register("UIDungeonMain", event.MONSTER_TRANS_BUILDING, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];
        grid:onMonsterTransBuilding();
    end);

    -- 注册格子刷怪事件回调
    EventMgr.register("UIDungeonMain", event.GRID_REFRESH_MONSTER, function(args)
        local pos = args.pos;
        local classId = args.classId;
        local desc = args.desc;
        local grid = self.grids[pos];
        local isHide = args.isHide;

        local gridOpenType = false;

        -- 如果是西洋棋召唤的
        if ActivityDungeonM.isInActivityBossLayer() then
            local summonMonster = ME.user.chessBossAppearSummon;
            if type(summonMonster) == "table"
                and table.indexOf(summonMonster, grid.gridData:getPos()) >= 0 then
                gridOpenType = true;
            end
        end

        -- 如果要隐藏的话，那么就先不去刷怪了
        if isHide then
            grid.isHide = true;
            return;
        end

        -- 记录下刚刷出来的怪
        grid.actionManager.newMonster = DungeonM.getGridByPos(pos).monster;

        if desc == "tent" then
            local function doTentShake()
                -- 播放帐篷抖动
                -- 如果是在boss层的话，去它自己的模块拿去数据
                local tentGridData;
                if DungeonBossM.isInBossLayer() then
                    local tentPos = BossTentM.getCurTentPos();
                    tentGridData = BossTentM.isTentGrid(tentPos);
                else
                    tentGridData = DungeonTentM.findTentGrid();
                end

                if not tentGridData then
                    return 0;
                end

                local tentGrid = self.grids[tentGridData:getPos()];
                playTentShake(tentGrid.item);

                local function appearShadow()
                    -- 播放怪物出现的黑影
                    playMonsterAppearShadow(grid);
                end
                return 0.1;
            end
            grid.actionManager:addAction(grid.gridData, doTentShake, "doTentShake", {}, GRID_ACTION_PHASE_GRID_OPENED);
        end

        local function doRefreshMonster()
            -- 采用静默方式打开格子
            grid:onRefreshMonster(pos, desc);
            self:openGrid(pos, grid.type, gridOpenType);
            grid:monsterFadeIn();

            -- 如果rank比较大
            local rank;
            if grid.gridData.monster then
                rank = grid.gridData.monster:getRank();
            else
                rank = 0;
            end
            if rank > 0 then
                for _, g in pairs(self.grids) do
                    if g ~= grid and g.gridData.monster and g.gridData.monster:getRank() < rank then
                        g:updateHPAndAttack();
                    end
                end
            end

            return 0.4;
        end

        grid.actionManager:addAction(grid.gridData, doRefreshMonster, "doRefreshMonster", {}, GRID_ACTION_PHASE_MONSTER_APPEAR);
        EventMgr.fire(event.PLAYER_MOVE, pos);
    end);

    -- 注册格子转换事件回调
    EventMgr.register("UIDungeonMain", event.TRANSFORM_GRID, function(args)
        local pos     = args.pos;
        local isDelay = args.isDelay;

        local grid = self.grids[pos];

        local function doTransform()
            -- 先转换格子数据
            grid.gridData = DungeonM.getGridByPos(pos);
            -- 先缓存起来
            self.cacheTransformGrids = self.cacheTransformGrids or {};
            table.insert(self.cacheTransformGrids, pos);

            if isDelay then
                if type(isDelay) == "string" and isDelay == "summon" then
                    -- 如果界面不是最新的，才需要return掉了
                    if DungeonM.currentLayer() ~= self.curFloor
                        or DungeonInstanceM.getCurrentInstanceId() ~= self.instanceId then
                        return;
                    end
                else
                    -- 需要延迟播放
                    return;
                end
            end

            local function callFunc()
                -- 刷新格子
                self:doCacheTransform(0.1);
            end

            performWithDelay(self, callFunc, 0.1);

            return 0.2;
        end

        grid.actionManager:addAction(grid.gridData, doTransform, "doTransform", {}, GRID_ACTION_PHASE_MONSTER_APPEAR);
        EventMgr.fire(event.PLAYER_MOVE, pos);
    end);

    -- 注册村民奖励事件回调
    EventMgr.register("UIDungeonMain", event.VILLAGER_BONUS, function(args)
        local pos   = args.pos;
        local bonus = args.bonus;
        self.grids[pos]:onVillagerEvent(bonus);

        -- 如果有魔藤，刷新其攻击和血量显示
        self:redrawBineAttackAndHp();
    end);

    -- 注册宝藏奖励事件回调
    EventMgr.register("UIDungeonMain", event.TREASURE_BONUS, function(args)
        local pos   = args.pos;
        local bonus = args.bonus;

        local tip = TreasureM.getBonusDesc(bonus);
        if type(tip) == "string" and #tip > 0 then
            -- alert(tip);
            local list = splitString(tip, "\n");
            splitAlert(list);
        end

        self.grids[pos]:onTreasureEvent(bonus);
    end);

    -- 注册翻找垃圾堆事件
    EventMgr.register("UIDungeonMain", event.RUBISH_EVENT, function(args)
        local pos   = args.pos;
        local bonus = args.bonus;
        self.grids[pos]:onRubbishEvent(bonus);
    end);

    -- 注册雕像事件
    EventMgr.register("UIDungeonMain", event.STATUE_EVENT, function(args)
        local pos   = args.pos;
        local bonus = args.bonus;
        self.grids[pos]:onStatueEvent(bonus);
    end);

    -- 未来猫任务
    EventMgr.register("UIDungeonMain", event.FINISH_DORAEMON, function(args)
        local pos   = args.pos;
        local bonus = args.bonus;
        self.grids[pos]:onFinishDoraemonTask(bonus);
    end);

    -- 回答仙女问题
    EventMgr.register("UIDungeonMain", event.ANSWER_FAIRY_QUETION, function(args)
        local pos   = args.pos;
        local grid = self.grids[pos];

        -- 属性可能改变了，刷新一下界面
        self:updateUI();

        if grid.gridData.state ~= GRID_STATE_DISABLE then
            grid:onFairyTalk(args);
        else
            -- 消失
            playBuildingDisappear(grid);
        end
    end);

    -- 注册通用建筑奖励事件
    EventMgr.register("UIDungeonMain", event.COMM_BUILDING_BONUS, function(args)
        local pos        = args.pos;
        local bonus      = args.bonus;
        local remainGrid = args.remainGrid;
        local curNum     = (nil == args.curNum) and nil or args.curNum;
        local tips       = args.tips;
        local desc       = args.desc;

        -- 如果这个建筑有光环属性，需要刷新下所有怪物的攻击血量
        local grid = self.grids[pos];
        if grid.gridData.assist_prop then
            for _, g in pairs(self.grids) do
                g:updateHPAndAttack();
            end

            -- 刷新状态条
            self.dungeonStatusBar:updateBar();
        end

        local function okFunc()
            -- 一般效果
            local function bonusEffect()
                local grid = self.grids[pos];
                local bonusList = bonus["bonus"];
                if grid.gridData.item_display then
                    grid:onPickUp(bonusList);
                    return;
                end

                local effectId = grid.gridData.effect_id;
                if type(effectId) == "number" then
                    local offset = grid.gridData.effect_offset or {0, 0};
                    playEffect(grid, effectId, offset[1], offset[2]);
                elseif type(effectId) == "string" then
                    playBuildingDisappearEffect(grid, effectId);
                end

                grid:onBuildingBonus(bonus, remainGrid, curNum);

                if tips then
                    -- 抽取掉颜色信息
                    tips = extractString(tips);
                    local list = splitString(tips, "\n");
                    splitAlert(list);
                end
            end

            if desc == "chair" then
                -- 电椅效果
                playElectricChairEffect();
                bonusEffect();
            elseif desc == "massage_room" then
                -- 按摩房
                if bonus["bonus"] then
                    local title = getLocStr("massage_room_title");
                    local msg = getLocStr("massage_room_extra_bonus");
                    local okText = getLocStr("btn_text_thanks2");
                    confirm2(CONFIRM_TYPE_OK, title, msg, bonusEffect, nil, okText);
                else
                    bonusEffect();
                end
            else
                bonusEffect();
            end
        end

        local bonusList = bonus["bonus"];
        -- 奖励是机器人时，弹出提示框
        if bonusList and type(bonusList[1]) == 'number' and bonusList[1] == 8 then
            showBonusNotifyForm(getLocStr("achive_award"), {bonusList}, okFunc);
            return;
        elseif bonusList and type(bonusList[1]) == 'table' then
            for k, v in pairs(bonusList) do
               if v[1] == 8 then
                    showBonusNotifyForm(getLocStr("achive_award"), bonusList, okFunc);
                    return;
               end
            end
        end

        okFunc();
    end);

    -- 注册被偷窃事件
    EventMgr.register("UIDungeonMain", event.BE_STOLEN, function(args)
        local pos        = args.pos;
        local bonus      = args.bonus;
        local keep       = args.keep;
        local noAlert    = args.noAlert;

        -- 偷窃提示
        local item = bonus["bonus"];
        local itemName;
        if type(item[1]) == "table" then
            item = item[1];
        end
        if item[1] == 1 then
            itemName = ItemM.query(item[2], "name");
        elseif item[1] == 2 then
            itemName = FieldsM.getFieldName(item[2]);
        end

        if noAlert ~= true then
            local msg = string.format(getLocStr("stealing_tip"), itemName, item[3]);
            alert(msg);
        end

        self.grids[pos]:beStolen(keep);

        self.grids[pos]:onBuildingBonus(bonus, true);

        local grid = DungeonM.getGridByPos(pos);
        if grid:isMonster() then
            -- 尝试被偷袭的发言
            self.grids[pos]:showAnimationTalk(MONSTER_ACTION_STOLENED);
        end
    end);

    -- 注册魔神雕像献祭事件
    EventMgr.register("UIDungeonMain", event.DEMON_SUBMIT, function(args)
        local pos        = args.pos;
        local bonus      = args.bonus;
        local isUpgrade  = args.isUpgrade;
        local remainGrid = true;

        if isUpgrade then
            playDemonSumitBonusEffect(self.grids[pos], args.classify, args.upgradeLevel, args.bonusList);
        end
    end);

    -- 注册种植魔藤的事件
    EventMgr.register("UIDungeonMain", event.PLANT_BINE, function(args)
        local pos = args.pos;
        self.grids[pos]:onPlantBine();
    end);

    -- 注册种植魔藤的事件
    EventMgr.register("UIDungeonMain", event.GARDEN_RESET, function(args)
        local pos = args.pos;

        -- 重新初始化格子
        self.grids[pos]:initByType();
    end);

    -- 注册魔藤受创的回调
    EventMgr.register("UIDungeonMain", event.BINE_DAMAGED, function(args)
        local damage = args.damage;
        local bine   = args.bine;

        -- 魔藤已经死亡，不处理
        if bine:isDead() then
            return;
        end

        local pos = bine:getPos();
        local grid = self.grids[pos];

        -- 刷新攻击和血量显示
        grid:redrawBineAttackAndHp();
        grid:showHpDamaged(damage);
        grid:updateCountDown();
    end);

    -- 注册魔藤施法前摇开始的回调
    EventMgr.register("UIDungeonMain", event.BINE_PRE_CAST, function(args)
        local targetPos = args.pos;
        local skillId   = args.skillId;
        local bine = ME.user.bine;
        local pos = bine:getPos();
        local grid = self.grids[pos];

        -- 临时记录施法信息，待施法前摇结束后播放施法动作
        bine.dbase:setTemp("cast_temp", { ["skill_id"] = skillId, ["target_pos"] = targetPos, });

        -- 播放施法前摇动作
        grid:switchBineAction(BINE_ACTION_PRE_CAST);
    end);

    -- 关注魔藤施法前摇结束的事件
    EventMgr.register("UIDungeonMain", event.BINE_PRE_CAST_OVER, function()
        local bine = ME.user.bine;
        local pos = bine:getPos();
        local grid = self.grids[pos];

        local castInfo = bine.dbase:queryTemp("cast_temp");
        if not castInfo then
            -- 目标不存在，直接切换至待机动作
            trace("UIDungeonMain", "魔藤施法前摇结束，攻击目标不存在，切回待机动作。");
            grid:switchBineAction(BINE_ACTION_IDLE);
            return;
        end

        local skillId   = castInfo["skill_id"];
        local targetPos = castInfo["target_pos"];

        -- 使用技能
        if true ~= DungeonActionM.go("bine_use_skill", targetPos, skillId) then
            trace("UIDungeonMain", "魔藤施法前摇结束，使用技能失败，切回待机动作。");
            grid:switchBineAction(BINE_ACTION_IDLE);
            return;
        end

        local targetNode = self.grids[targetPos];

        -- 播放施法动作
        trace("UIDungeonMain", "播放魔藤施法动作，魔藤位置：%d，目标位置：%d", pos, targetPos);
        playBineCast(grid, targetNode);

        -- 刷新技能气泡
        grid:redrawBineSkill();
    end);

    -- 注册魔藤攻击前摇开始的回调
    EventMgr.register("UIDungeonMain", event.BINE_PRE_ATTACK, function(args)
        local targetPos = args.pos;
        local bine = ME.user.bine;
        local pos = bine:getPos();
        local grid = self.grids[pos];

        -- 魔藤已经死亡，不处理
        if bine:isDead() then
            return;
        end

        -- 记录目标位置，待攻击前摇结束后播放攻击动作
        bine.dbase:setTemp("target_pos", targetPos);

        -- 播放攻击前摇动作
        trace("UIDungeonMain", "播放魔藤攻击前摇动作");
        grid:switchBineAction(BINE_ACTION_PRE_ATTACK);
    end);

    -- 关注魔藤攻击前摇结束的事件
    EventMgr.register("UIDungeonMain", event.BINE_PRE_ATTACK_OVER, function()
        local bine = ME.user.bine;
        local pos = bine:getPos();
        local grid = self.grids[pos];

        local targetPos = bine.dbase:queryTemp("target_pos");
        if not targetPos then
            -- 目标不存在，直接切换至待机动作
            trace("UIDungeonMain", "魔藤攻击前摇结束，攻击目标不存在，切回待机动作。");
            grid:switchBineAction(BINE_ACTION_IDLE);
            return;
        end

        local targetNode = self.grids[targetPos];

        -- 播放攻击动作
        trace("UIDungeonMain", "播放魔藤攻击动作，魔藤位置：%d，目标位置：%d", pos, targetPos);
        playBineAttack(grid, targetNode);
    end);

    -- 关注魔藤攻击结束的事件
    EventMgr.register("UIDungeonMain", event.BINE_ATTACK_OVER, function()
        local bine = ME.user.bine;
        local pos = bine:getPos();
        local grid = self.grids[pos];

        -- 攻击完毕，切换至待机状态
        trace("UIDungeonMain", "魔藤攻击完毕，切换至待机状态。");
        grid:switchBineAction(BINE_ACTION_IDLE);

        -- 触发出手事件
        EventMgr.fire(event.PLAYER_MOVE, pos);
    end);

    -- 注册魔藤升级的事件
    EventMgr.register("UIDungeonMain", event.BINE_LEVEL_UP, function(args)
        local bine = ME.user.bine;
        local pos = bine:getPos();
        local grid = self.grids[pos];

        -- 刷新技能气泡
        grid:redrawBineSkill();

        -- 刷新攻击和血量显示
        grid:redrawBineAttackAndHp();
    end);

    -- 注册恢复魔藤的事件
    EventMgr.register("UIDungeonMain", event.CURE_BINE, function(args)
        if args and args.skillId then
            -- 如果是魔藤施法使自身恢复，则这里先不做处理，待施法结束后再处理
            return;
        end

        local bine = ME.user.bine;
        local pos = bine:getPos();
        local grid = self.grids[pos];

        -- 刷新技能气泡
        grid:redrawBineSkill();

        -- 刷新攻击和血量显示
        grid:redrawBineAttackAndHp();
    end);

    -- 注册魔藤施法结束的事件
    EventMgr.register("UIDungeonMain", event.BINE_CAST_OVER, function(args)
        local bine = ME.user.bine;
        local pos = bine:getPos();
        local grid = self.grids[pos];

        -- 施法完毕，切换至待机状态
        trace("UIDungeonMain", "魔藤施法完毕，切换至待机状态。");
        grid:switchBineAction(BINE_ACTION_IDLE);

        -- 刷新攻击和血量显示
        grid:redrawBineAttackAndHp();
    end);

    -- 注册魔藤复活的事件
    EventMgr.register("UIDungeonMain", event.BINE_REVIVE, function(args)
        local bine = ME.user.bine;
        local pos = bine:getPos();
        local grid = self.grids[pos];

        -- 刷新技能气泡
        grid:redrawBineSkill();

        -- 刷新攻击和血量显示
        grid:redrawBineAttackAndHp();
        grid:updateCountDown();

        -- 播放待机动作
        grid:switchBineAction(BINE_ACTION_IDLE);
    end);

    -- 关注魔藤死亡的事件
    EventMgr.register("UIDungeonMain", event.BINE_DIE, function(args)
        local bine = ME.user.bine;
        local pos = bine:getPos();
        local grid = self.grids[pos];

        alert(getLocStr("bine_die"));

        -- 刷新技能气泡
        grid:redrawBineSkill();

        -- 刷新攻击和血量显示
        grid:redrawBineAttackAndHp();

        -- 播放死亡动作
        grid:switchBineAction(BINE_ACTION_DIE);
    end);

    -- 关注魔藤学习技能事件
    EventMgr.register("UIDungeonMain", event.BINE_LEARN_SKILL, function(args)
        local bine = ME.user.bine;
        local pos = bine:getPos();
        local grid = self.grids[pos];

        -- 刷新技能气泡
        grid:redrawBineSkill();

        -- 刷新攻击和血量显示
        grid:redrawBineAttackAndHp();

        -- 刷新冈布奥属性
        self:updateUI();
    end);

    -- 关注魔藤升级技能事件
    EventMgr.register("UIDungeonMain", event.BINE_UPGRADE_SKILL, function(args)
        local bine = ME.user.bine;
        local pos = bine:getPos();
        local grid = self.grids[pos];

        -- 刷新技能气泡
        grid:redrawBineSkill();

        -- 刷新攻击和血量显示
        grid:redrawBineAttackAndHp();

        -- 刷新冈布奥属性
        self:updateUI();
    end);

    -- 关注元素圣坛提交水晶的事件
    EventMgr.register("UIDungeonMain", event.SUBMIT_ELEM, function(args)
        local elem    = args.element;
        local isFinal = args.isFinal;
        local pos     = args.pos;
        local grid    = self.grids[pos];

        grid:onSubmitElem(elem, isFinal);
    end);

    -- 关注元素圣坛提交水晶的事件
    EventMgr.register("UIDungeonMain", event.SUBMIT_LORD_ELEM, function(args)
        local elem    = args.element;
        local isFinal = args.isFinal;
        local pos     = args.pos;
        local grid    = self.grids[pos];

        grid:onSubmitElem(elem, isFinal);
    end);

    -- 关注元素圣坛提交失败的事件
    EventMgr.register("UIDungeonMain", event.ELEM_FAIL_SUBMIT, function(args)
        local elem = args.elem;
        local pos = args.pos;
        local grid = self.grids[pos];
        local direction = args.direction;

         -- 获取元素颜色
        grid:redrawFailSub(direction, elem);
        local color = ElementAltarM.queryElement(elem, "color");
        local function playFaliEffect()
            playElemMagicFailEffect(color, direction, grid);

            local class   = grid.gridData.class;
            if DungeonM.query(class, "rule") == "special_element_lord" then
                -- 玩家身上播放闪电效果
                -- 屏幕震动
                playShakeEffect(UIDungeonMgr.getCurLevel(), 0.03, 4, 4);
                local effectParent = UIDungeonMgr:getCurLevel():getEffectParent();
                local heroPos = UIDungeonMgr.getCurLevel():getHeroPos();

                -- 雷云+闪电降临光效
                local effectId = 1259;
                local offset = {10, -65};
                playEffect(effectParent, effectId, heroPos.x + offset[1], heroPos.y + offset[2]);

                -- 播放玩家受创动作
                UIDungeonMgr:getCurLevel():switchHeroAction(HERO_ACTION_DAMAGED);
            end
        end
        performWithDelay(self, playFaliEffect, 0.3);
    end);

    -- 关注元素射线伤害事件
    EventMgr.register("UIDungeonMain", event.ELEM_DAMAGE, function(args)
        local elem      = args.elem;
        local target    = args.target;
        local pos       = args.pos;
        local skillId   = args.skillId;
        local direction = args.direction;
        local isFinal   = args.isFinal;
        local grid      = self.grids[pos];
        local gridData  = DungeonM.getGridByPos(pos);

        local function doElemDamage()
            local duration = 0;

            -- 尚未圆满，播放元素射线光效(如果时特殊圣坛，圆满后也要做伤害)
            if not isFinal or gridData.type == GRID_TYPE_ELEMENT_LORD then
                -- 获取元素颜色

                local color = ElementAltarM.queryElement(elem, "color");

                if not target then
                    if isFinal then
                        local function callFunc()
                            -- 抛出圣坛召唤事件
                            EventMgr.fire(event.TRANS_TO_RUINS, { ["pos"] = pos, ["elem"] = element,
                                ["class"] = class, ["isFinal"] = isFinal, });
                        end
                        -- 没有目标
                        duration = playElemMagicEffect(color, direction, grid, nil, callFunc);
                        return duration;
                    else
                        duration = playElemMagicEffect(color, direction, grid);
                        return duration;
                    end
                end

                local targetPos = target:getPos();

                trace("UIDungeonMain", "朝向为(%s)的水晶对位置为(%d)的目标造成伤害。", direction, targetPos);

                -- 播放水晶施法特效
                local targetNode = self.grids[targetPos];
                if isFinal then
                    local function callBackF()
                        -- 播放普通施法动画
                        -- local params = { ["magic"] = skillId, };
                        -- playElemBoomEffect(grid, params);

                        -- 抛出圣坛召唤事件
                        EventMgr.fire(event.TRANS_TO_RUINS, { ["pos"] = pos, ["elem"] = element,
                                ["class"] = class, ["isFinal"] = isFinal, });
                    end
                    duration = playElemMagicEffect(color, direction, grid, targetNode, callBackF, true);
                elseif gridData.type == GRID_TYPE_ELEMENT_LORD then
                    duration = playElemMagicEffect(color, direction, grid, targetNode, nil, true);
                else
                    duration = playElemMagicEffect(color, direction, grid, targetNode);
                end
            else
                -- 已经圆满
                trace("UIDungeonMain", "元素圣坛已经圆满，播放圣坛爆裂动画。");

                -- 播放普通施法动画
                local params = { ["magic"] = skillId, ["gridType"] = gridData.type};
                duration = playElemBoomEffect(grid, params);
            end

            return duration;
        end

        grid.actionManager:addAction(grid.gridData, doElemDamage, "doElemDamage", {}, GRID_ACTION_PHASE_GRID_OPENED);

        EventMgr.fire(event.PLAYER_MOVE, grid.gridData:getPos());
    end);

    -- 关注元素圣坛奖励事件
    EventMgr.register("UIDungeonMain", event.ELEM_ALTAR_BONUS, function(args)
        local bonus   = args.bonus;
        local pos     = args.pos;
        local isFinal = args.isFinal;
        local grid    = self.grids[pos];
        local gridType = grid.gridData.type;

        -- 如果已经圆满
        if isFinal then
            -- 播放元素圣坛爆裂光效
            local params = { ["bonus"] = bonus, ["gridType"] = gridType,};
            local function boomEffect()
                duration = playElemBoomEffect(grid, params);
                return duration;
            end

            grid.actionManager:addAction(grid.gridData, boomEffect, "boomEffect", {}, GRID_ACTION_PHASE_GRID_OPENED);
        end
    end);

    -- 关注元素圣坛召唤事件
    EventMgr.register("UIDungeonMain", event.ELEM_SUMMON, function(args)
        local pos     = args.pos;
        local class   = args.class;
        local isFinal = args.isFinal;
        local grid    = self.grids[pos];
        local gridType = grid.gridData.type;

        -- 如果已经圆满
        if isFinal then
            -- 播放元素圣坛爆裂光效
            local params = { ["summon"] = class, ["gridType"] = gridType};
            local function boomEffect()
                duration = playElemBoomEffect(grid, params);
                return duration;
            end

            grid.actionManager:addAction(grid.gridData, boomEffect, "boomEffect", {}, GRID_ACTION_PHASE_GRID_OPENED);
        end
    end);

    -- 关注元素圣坛召唤事件
    EventMgr.register("UIDungeonMain", event.TRANS_TO_RUINS, function(args)
        local pos     = args.pos;
        local class   = args.class;
        local isFinal = args.isFinal;
        local grid    = self.grids[pos];

        -- 如果已经圆满
        if isFinal then
            -- 播放元素圣坛爆裂光效
            local params = { ["summon"] = class };

            local function boomEffect()
                duration = playLordBoomEffect(grid, params);
                return duration;
            end

            grid.actionManager:addAction(grid.gridData, boomEffect, "boomEffect", {}, GRID_ACTION_PHASE_GRID_OPENED);
        end
    end);

    -- 关注元素圣坛日记刷新的事件
    EventMgr.register("UIDungeonMain", event.ELEM_DIARY_APPEND, function(args)
        -- 提示玩家
        performWithDelay(self, function() alert(getLocStr("elem_diary_append")) end, 1.2);
    end);

    -- 关注使用道具攻击目标的事件
    EventMgr.register("UIDungeonMain", event.PROPERTY_ATTACK, function(args)
        local classId = args.classId;
        local target  = args.target;
        local source  = args.source;
        local bonus   = args.bonus;
        local moreTarget = args.moreTarget;
        local moreTargetList;
        local treeHouse = false;

        local targetPos = target:getPos();
        local targetNode = self.grids[targetPos];

        if target.type ~= OBJECT_TYPE_MONSTER and
           not targetNode.gridData.monster then
            return;
        end

        -- 如果场上有魔镜，飞向魔镜
        local mirrorPos = MagicMirrorM.getMagicMirror("property");
        if mirrorPos >= 0 then
            targetNode = self.grids[mirrorPos];
            targetPos = mirrorPos;
            targetNode.mirror = true;
        end

        local function doPropertyAttack()
            local duration = 0;

            local icon = ItemM.query(classId, "icon");
            local iconPath = getItemIconPath(icon);

            local heroPos = self:getHeroPos();

            if source and source.isTreeHouse and source:isTreeHouse() then
                -- 修正为树屋位置
                local treePos = source:getPos();
                heroPos = cc.p(self.grids[treePos]:getPosition());
                treeHouse = true;
            end

            -- 计算额外显示效果的节点
            if moreTarget and type(moreTarget) == "table" then
                moreTargetList = {};
                for _, elseTarget in pairs(moreTarget) do
                    local elseNodeInfo = {};
                    if elseTarget:isBoss() then
                        elseNodeInfo[1] = "boss";
                    else
                        elseNodeInfo[1] = "normal";
                    end
                    local elsePos = elseTarget:getPos();
                    elseNodeInfo[2] = self.grids[elsePos];
                    table.insert(moreTargetList, elseNodeInfo);
                end
            end

            trace("UIDungeonMain", "播放道具(%d)攻击特效，目标格子(%d)", classId, targetPos);

            -- 树屋可能提交的不是道具
            local propertyInfo = PropertyM.query(classId);
            if not propertyInfo and not treeHouse then
                error("获取不到道具(%s)的信息", tostring(classId));
            end

            -- 获取配置
            local propDbase = propertyInfo and propertyInfo["dbase"] or EquipM.query(classId, "dbase");

            -- 附加信息
            local extraPara = {["moreTargetList"] = moreTargetList, ["treeHouse"] = treeHouse};

            local dbase = {};
            if target.type == OBJECT_TYPE_MONSTER then
                dbase = MonsterM.query(target:getClassId(), "dbase");
            end

            -- 开始投掷动画播放
            -- 播放逻辑：
            -- 1.有些动画比较复杂(如沙漠之鹰)，在property.csv的dbase配上attack_anim和对应的字段，单独处理
            -- 2.去查找配表中的信息，播放通用动画(支持路径以及击中特效)
            -- 3.如果都没有配置，那么播放默认的旋转投掷，普通受创特效
            -- 如果是需要单独处理的特效
            local animType = propertyInfo and propDbase["attack_anim"] or "default";
            if animType == "eagle" then
                -- 沙漠之鹰
                AudioM.playFx("shooting");
                duration = playEagleAttack(heroPos, targetNode, extraPara);
                return duration;
            elseif animType == "cannon" and not targetNode.mirror then
                -- 炎龙刃效果
                local nodes = {};
                for i = 1, TOTAL_GRID_NUM do
                    local grid = self.grids[i];
                    if i % GRID_COL_NUM == targetPos % 5 then
                        table.insert(nodes, grid);
                    end
                end
                local sourcePos = 25 + iif(targetPos % 5 ~= 0, targetPos % 5, 5);
                local sourceNode = self.grids[sourcePos];
                duration = playFireBladeEffect(sourceNode, {["up"] = nodes}, {["borrow"] = true});
                return duration;
            end

            -- 轨迹
            local moveType = propDbase["move_type"];
            moveType = moveType and moveType or LaunchType.ROTATE;

            -- 打击特效，如果是boss的话，播放特殊的特效
            local effectId;
            local effectOffset;
            local bossEffect = propDbase["boss_effect"];
            local needPlay = bossEffect and dbase["boss_click_range"];
            if needPlay then
                effectId = (bossEffect and bossEffect["effect_id"]) and bossEffect["effect_id"] or 3006;
                effectOffset = (bossEffect and bossEffect["offset"]) and cc.p(bossEffect["offset"][1], bossEffect["offset"][2]) or cc.p(0, -50);
            else
                local effect = propDbase["effect"];
                effectId = (effect and effect["effect_id"]) and effect["effect_id"] or 3006;
                effectOffset = (effect and effect["offset"]) and cc.p(effect["offset"][1], effect["offset"][2]) or cc.p(0, 10);
            end

            -- 音效
            local audioName = propDbase["audio"];
            audioName = audioName and audioName or "throw";

            -- 光效拖尾
            local tailId;
            if moveType == LaunchType.STRAIGHT then
                tailId = propDbase["rotate_offset"] and propDbase["rotate_offset"] or 805;
            end

            -- 旋转微调
            local rotateOffset = propDbase["rotation"] and propDbase["rotation"] or 45;

            -- 图片的长度
            local iconLength = propDbase["icon_length"] and propDbase["icon_length"] or 55;

            -- 播放通用的投掷特效
            duration = playPropertyHitAnimation(targetNode, iconPath, heroPos, moveType, effectId, effectOffset, propDbase, audioName, extraPara, bonus, tailId, rotateOffset, false, iconLength);
            return duration;
        end
        targetNode.actionManager:addAction(targetNode.gridData, doPropertyAttack, "doPropertyAttack", {}, GRID_ACTION_PHASE_THROW_FLY);
        EventMgr.fire(event.PLAYER_MOVE, targetNode.gridData:getPos());
    end);

    -- 关注使用道具施法的事件
    EventMgr.register("UIDungeonMain", event.PROPERTY_SKILL, function(args)
        local classId = args.classId;
        local target  = args.target;
        local targetPos = target:getPos();
        local targetNode = self.grids[targetPos];

        trace("UIDungeonMain", "播放道具(%d)施法特效，目标格子(%d)", classId, targetPos);

        local propertyInfo = PropertyM.query(classId);
        if not propertyInfo then
            error("获取不到道具(%s)的信息", tostring(classId));
        end

        local animType = propertyInfo["dbase"]["attack_anim"];
        if animType == "ball" then
            -- 杂技彩球
            -- 音效
            -- AudioM.playFx("throw");
            -- 获取目标所在行的节点
            local targetCoord = DungeonM.convertToCoor(targetPos);
            local targetNodes = {};
            for i = 1, DUNGEON_WIDTH do
                local pos = DungeonM.convertToPos(i, targetCoord.y);
                table.insert(targetNodes, self.grids[pos]);
            end

            playBallAttackEffect(targetNode, targetNodes);
        end
    end);

    -- 关注投掷类攻击事件
    EventMgr.register("UIDungeonMain", event.SKILL_ATTACK, function(args)
        local effectInfo = args["effect_info"];
        local icon = ItemM.query(effectInfo["icon_id"], "icon");
        local iconPath = getItemIconPath(icon);
        local heroPos = self:getHeroPos();
        local moveType = effectInfo["move_type"];
        local effectId = effectInfo["effect_id"];
        local effectOffset = effectInfo["offset"] and effectInfo["offset"] or cc.p(0, 10);
        local audioName = effectInfo["audio"] and effectInfo["audio"] or "throw";
        local tailId = effectInfo["tail_id"];
        local rotateOffset = effectInfo["rotate_offset"] and effectInfo["rotate_offset"] or 45;
        local isSync = effectInfo["is_sync"] and effectInfo["is_sync"] or false;
        local iconLength = effectInfo["icon_length"] and effectInfo["icon_length"] or 55;

        local targetNode;
        if type(effectInfo["pos"]) == "table" then
            targetNode = {};
            for _, c in pairs(effectInfo["pos"]) do
                table.insert(targetNode, self.grids[c]);
            end
        else
            targetNode = self.grids[effectInfo["pos"]];
        end

        -- 播放通用的投掷特效
        playPropertyHitAnimation(targetNode, iconPath, heroPos, moveType, effectId, effectOffset, nil, audioName, nil, nil, tailId, rotateOffset, isSync, iconLength);
    end);

    -- 自身奖励道具使用事件
    EventMgr.register("UIDungeonMain", event.SELF_BONUS_ITEM_USED, function(args)
        self:handleNextFloorGainBonus(args);
    end);

    -- 注册添加BUFF事件处理
    EventMgr.register("UIDungeonMain", event.ADD_BUFF, function(params)
        local target = params["target"];
        local prop = params["prop"];
        if target ~= ME.user then
            local targetPos = target:getPos();
            local targetNode = self.grids[targetPos];

            -- 处于石化状态的格子不播放特效
            local pos = target:getOwner();
            if pos and type(pos) == "number" then
                local uiLevel = UIDungeonMgr.getCurLevel();
                local uiGrid  = uiLevel:getAllGrids()[pos];

                if uiGrid:getIsInStone() then
                    return;
                end
            end

            playAddBuffEffect(targetNode);
        end
    end);

    -- 注册添加DEBUFF事件处理
    EventMgr.register("UIDungeonMain", event.ADD_DEBUFF, function(params)
        local target = params["target"];
        local prop = params["prop"];
        if target ~= ME.user then
            local targetPos = target:getPos();
            local targetNode = self.grids[targetPos];

            -- 处于石化状态的格子不播放特效
            local pos = target:getPos();
            if pos and type(pos) == "number" then
                local uiLevel = UIDungeonMgr.getCurLevel();
                local uiGrid  = uiLevel:getAllGrids()[pos];

                if uiGrid and uiGrid:getIsInStone() then
                    return;
                end
            end

            playAddDeBuffEffect(targetNode);
        end
    end);

    -- 注册属性增加事件处理
    EventMgr.register("UIDungeonMain", event.ADD_PROP, function(params)
        local effectId = params["effect_id"];
        if type(effectId) == "string" and effectId == "elite" then
            -- 降低精英怪属性
            for _, monsterGrid in pairs(self.openMonsterGrids) do
                monsterGrid:updateHPAndAttack();
            end
        else
            playPropertyEffect(effectId);
        end
    end);

    -- 注册石柱关闭的事件回调
    EventMgr.register("UIDungeonMain", event.CLOSED_STONE, function(args)
        local pos = args.pos;
        local id  = args.id;
        local grid = self.grids[pos];
        grid:onCloseStone(id);

        -- 属性可能提升了，刷新一下界面
        self:updateUI();
        -- 刷新状态条
        self.dungeonStatusBar:updateBar();
    end);

    -- 关注刷新状态
    EventMgr.register("UIDungeonMain", event.CLEAR_DEBUFF, function(args)
        -- 属性可能提升了，刷新一下界面
        self:updateUI();
        -- 刷新状态条
        self.dungeonStatusBar:updateBar();
    end);

    -- 注册学习石柱技能的事件回调
    EventMgr.register("UIDungeonMain", event.STUDY_STONE, function(args)
        local pos = args.pos;
        local id  = args.id;
        local grid = self.grids[pos];
        grid:onStudyStoneSkill(id);

        -- 属性可能提升了，刷新一下界面
        self:updateUI();
    end);

    -- 注册施法的事件回调
    EventMgr.register("UIDungeonMain", event.APPLY_SKILL, function(args)
        -- 属性可能提升了，必要时刷新界面
        self:updateUI();

        local pos     = args.pos;
        local skillId = args.skillId;
        local source  = args.source;

        local grid = self.grids[pos];
        if not grid then
            return;
        end

        if source.type == OBJECT_TYPE_MONSTER then
            -- 如果是冈布奥怪施法
            if source:isSlime() then
                -- 如果是主动技能
                if ArenaM.isActiveSkill(source, skillId) then
                    -- 播放冈布奥虚影效果
                    local petId = ArenaM.querySlime(source.classId, "pet_id");
                    if type(petId) == "number" then
                        local pos2 = self:getMonsterPos2(pos);
                        playSlimeVirtualShadowEffect(petId, pos2);
                    end

                    -- 播放施法动作
                    grid:switchMonsterAction(MONSTER_ACTION_CAST);
                else
                    -- 播放普通攻击动作
                    grid:switchMonsterAction(MONSTER_ACTION_ATTACK, { ["skillId"] = skillId, });
                end
            elseif ActivityDungeonM.isInActivityBossLayer() and ActivityBossM.isSpecialBoss(source) then
                -- 是活动副本特殊的冈布奥怪物
                if ActivityBossM.isActiveSkill(source, skillId) then
                    -- 播放冈布奥虚影效果
                    local petId = ActivityBossM.queryBoss(source.classId, "pet_id");
                    local dbase = ActivityBossM.queryBoss(source.classId, "dbase") or {};
                    if type(petId) == "number" and dbase["hide_shadow"]  ~= 1 then
                        local pos2 = self:getMonsterPos2(pos);
                        playSlimeVirtualShadowEffect(petId, pos2);
                    end

                    -- 播放施法动作
                    grid:switchMonsterAction(MONSTER_ACTION_CAST);
                else
                    -- 播放普通攻击动作
                    grid:switchMonsterAction(MONSTER_ACTION_ATTACK, { ["skillId"] = skillId, });
                end
            end

            -- 尝试攻击动作发言
            grid:showAnimationTalk(MONSTER_ACTION_ATTACK, skillId);
        end
    end);

    -- 注册开启格子（不影响周围格子局势）的事件回调
    EventMgr.register("UIDungeonMain", event.PURE_OPEN_GRID, function(args)
        local pos = args.pos;

        trace("UIDungeonMain", "格子(%d)自动开启", pos );
        local grid = self.grids[pos];
        grid:gotoOpen();

        -- 如果是石柱
        if grid.gridData.type == GRID_TYPE_STONE
            or grid.gridData.type == GRID_TYPE_KRYPTONITE then
            if MagicStoneM.isClosed() then
                -- 石柱已关闭，显示石柱已关闭的模型
                playStoneCloseEffect(grid);
            else
                -- 石柱未关闭，播放石柱禁魔特效
                playForbidMagicEffect(grid);
            end

            -- 必须有连通的临格
            local grids = DungeonM.getCurrentDungeon()
            local ok = GRID_OPEN_DISABLE;
            for _, p in pairs(DungeonM.getAdjoinGrids(pos)) do
                local g = grids[p];

                -- 在门的旁边，或者已翻开的“非障碍”旁边
                if  g.type == GRID_TYPE_DOOR or g.type == GRID_TYPE_GARDEN or
                    g.temp_refresh == true or  -- 是临时刷出的怪
                    g.state == GRID_STATE_DISABLE or
                    g.state ~= GRID_STATE_COVER and g.type ~= GRID_TYPE_BLOCK and
                    (g:isPublicGrid() == false or g.activeState or g:isMonster() and g.monster:isDead()) then
                    -- 有联通的临格
                    ok = GRID_OPEN_OK;
                    break;
                end
            end

            -- 联通的情况下尝试去激活
            if ok == GRID_OPEN_OK then
                self:activeDisplayGrid(pos, true);
            end
        end

        -- 暗之石柱
        if grid.gridData.type == GRID_TYPE_DARK_STONE then
            playDarkForbidMagicEffect(grid);
        end

        EventMgr.fire(event.PLAYER_MOVE);
    end);

    -- 关注下楼奖励事件
    EventMgr.register("UIDungeonMain", event.LAYER_BONUS_EVENT, function(args)
        local bonusList = args.bonusList;
        if bonusList ~= nil and #bonusList > 0 then
            showBonusNotifyForm(getLocStr("achive_award"), bonusList);
        end
    end);

    -- 关注宝物额外获得材料奖励事件
    EventMgr.register("UIDungeonMain", event.TREASURE_GET_MATERIAL, function(args)
        local bonus = args.bonus;
        if bonus ~= nil and #bonus > 0 then
            --local tip = string.format(getLocStr("treasure_get_material_tip"), ItemM.query(bonus[2], "name"), bonus[3]);
            performWithDelay(self, function() self:whenSelfBonus(bonus); end, 0.3);
        end
    end);

    -- 关注UI层开格子事件
    EventMgr.register("UIDungeonMain", event.UI_GRID_OPEN, function(args)
        local pos     = args.pos;
        local type    = args.type;
        local isQuite = args.isQuite;

        -- 如果是石柱
        if type == GRID_TYPE_STONE or type == GRID_TYPE_KRYPTONITE then
            local function showStoneEffect()
                local grid = self.grids[pos];

                if MagicStoneM.isClosed() then
                    -- 石柱已关闭，显示石柱已关闭的模型
                    playStoneCloseEffect(grid);
                else
                    -- 石柱未关闭，播放禁魔光效
                    -- 这里应该是暂离回来，因此只要停在最后一帧即可
                    playForbidMagicEffect(grid, isQuite);
                end
            end
            performWithDelay(self, showStoneEffect, 0.01);
        end

        -- 暗之石柱
        if type == GRID_TYPE_DARK_STONE then
            local function showStoneEffect()
                local grid = self.grids[pos];
                playDarkForbidMagicEffect(grid);
            end
            performWithDelay(self, showStoneEffect, 0.01);
        end

        -- 开格获得金币动画移到此处
        if not self.playingPickUpMoney or not self.playingPickUpMoney[pos] then
            return;
        end

        local params = self.playingPickUpMoney[pos];
        self.grids[pos]:onPickUp(params.bonus, params.newBonus, true);
        self.playingPickUpMoney[pos] = nil;
    end);

    -- 注册雇佣幽灵船的回调
    EventMgr.register("UIDungeonMain", event.EMPLOY_SHIP, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];
        grid:onEmployShip();
    end);

    -- 关注幽灵船学习技能事件
    EventMgr.register("UIDungeonMain", event.LEARN_SHIP_SKILL, function(args)
        local ship = ME.user.ship;
        local pos = ship:getPos();
        local grid = self.grids[pos];

        -- 刷新技能气泡
        grid:redrawShipSkill();

        -- 刷新攻击和血量显示
        grid:redrawShipAttackAndHp();

        -- 自动攻击倒计时
        grid:updateCountDown();

        for _, grid in pairs(self.grids) do
            -- 更新血量和攻击
            if grid.gridData:isOpened() and grid.gridData:isMonster() then
                grid:updateHPAndAttack();
            end
        end
    end);

    -- 幽灵船攻击事件
    EventMgr.register("UIDungeonMain", event.SHIP_ATTACK, function(args)
        local grid = self.grids[args.pos];

        -- 自动攻击倒计时
        grid:updateCountDown();
    end);

    -- 关注幽灵船升级技能事件
    EventMgr.register("UIDungeonMain", event.UPGRADE_SHIP_SKILL, function(args)
        local ship = ME.user.ship;
        local pos = ship:getPos();
        local grid = self.grids[pos];

        -- 刷新攻击和血量显示
        grid:redrawShipAttackAndHp();

        -- 自动攻击倒计时
        grid:updateCountDown();

        for _, grid in pairs(self.grids) do
            -- 更新血量和攻击
            if grid.gridData:isOpened() and grid.gridData:isMonster() then
                grid:updateHPAndAttack();
            end
        end
    end);

    -- 注册幽灵船施法前摇开始的回调
    EventMgr.register("UIDungeonMain", event.SHIP_PRE_CAST, function(args)
        local skillId   = args.skillId;
        local ship = ME.user.ship;
        local pos = ship:getPos();
        local grid = self.grids[pos];

        -- 临时记录施法信息，待施法前摇结束后播放施法动作
        ship.dbase:setTemp("cast_temp", { ["skill_id"] = skillId, });

        -- 抛出施法前摇结束事件，准备播放施法动画
        EventMgr.fire(event.SHIP_PRE_CAST_OVER);

        -- 播放施法动作
        grid:switchShipAction(SHIP_ACTION_CAST);
    end);

    -- 关注幽灵船施法前摇结束的事件
    EventMgr.register("UIDungeonMain", event.SHIP_PRE_CAST_OVER, function()
        local ship = ME.user.ship;
        local pos = ship:getPos();
        local grid = self.grids[pos];

        local castInfo = ship.dbase:queryTemp("cast_temp");
        if not castInfo then
            -- 目标不存在，直接切换至待机动作
            trace("UIDungeonMain", "幽灵船施法前摇结束，攻击目标不存在，切回待机动作。");
            grid:switchShipAction(SHIP_ACTION_IDLE);
            return;
        end

        local skillId = castInfo["skill_id"];

        -- 施放技能
        -- 等收到施法技能成功的事件后再播放相关动画
        if true ~= DungeonActionM.go("use_ship_skill", -1, skillId) then
            trace("UIDungeonMain", "幽灵船施法前摇结束，使用技能失败，切回待机动作。");
            grid:switchShipAction(SHIP_ACTION_IDLE);
            return;
        end
    end);

    -- 关注幽灵船成功施放技能的事件
    EventMgr.register("UIDungeonMain", event.USE_SHIP_SKILL, function(args)
        local posList = args.posList;
        local ship = ME.user.ship;
        local pos = ship:getPos();
        local grid = self.grids[pos];

        if type(posList) ~= 'table' or #posList == 0 then
            trace("UIDungeonMain", "幽灵船施法目标不存在。");
            return;
        end

        local targetNodes = {};
        for _, pos in pairs(posList) do
            local targetNode = self.grids[pos];
            table.insert(targetNodes, targetNode);
        end

        -- 播放施法动画
        playShipCast(grid, targetNodes);
    end);

    -- 注册幽灵船攻击前摇开始的回调
    EventMgr.register("UIDungeonMain", event.SHIP_PRE_ATTACK, function(args)
        local targetPos = args.pos;
        local shipPos = args.ship_pos;
        local ship = ME.user.ship;          -- 这样不一定取得到幽灵船，还未雇佣的情况
        if not ship then
            ship = self.grids[shipPos].gridData.monster;
        end

        local pos = ship:getPos();
        local grid = self.grids[pos];

        if targetPos <= 0 then
            -- 目标是玩家
            trace("UIDungeonMain", "播放幽灵船攻击动画，幽灵船位置：%d，目标位置：玩家", pos);
            playShipAttackHero(grid, self:getHeroPos());
            return;
        end

        local targetNode = self.grids[targetPos];
        local function attackAnimation()
            -- 播放攻击动画
            trace("UIDungeonMain", "播放幽灵船攻击动画，幽灵船位置：%d，目标位置：%d", pos, targetPos);
            playShipAttackMonster(grid, targetNode);

            return 1;
        end

        targetNode.actionManager:addAction(targetNode.gridData, attackAnimation, "castSkill", { }, GRID_ACTION_PHASE_THROW_FLY);
    end);

    -- 关注幽灵船攻击前摇结束的事件
    EventMgr.register("UIDungeonMain", event.SHIP_PRE_ATTACK_OVER, function()
        local ship = ME.user.ship;
        if not ship then
            for pos = 1, GRID_SIZE do
                local grid = DungeonM.getGridByPos(pos);
                if grid.type == GRID_TYPE_GHOST_SHIP then
                    ship = grid.monster;
                    break;
                end
            end
        end

        local pos = ship:getPos();
        local grid = self.grids[pos];

        -- 播放攻击动作
        grid:switchShipAction(SHIP_ACTION_ATTACK);
    end);

    -- 关注幽灵船攻击结束的事件
    EventMgr.register("UIDungeonMain", event.SHIP_ATTACK_OVER, function()
        local ship = ME.user.ship;
        if not ship then
            for pos = 1, GRID_SIZE do
                local grid = DungeonM.getGridByPos(pos);
                if grid.type == GRID_TYPE_GHOST_SHIP then
                    ship = grid.monster;
                    break;
                end
            end
        end

        local pos = ship:getPos();
        local grid = self.grids[pos];

        -- 攻击完毕，切换至待机状态
        trace("UIDungeonMain", "幽灵船攻击完毕，切换至待机状态。");
        grid:switchShipAction(SHIP_ACTION_IDLE);

        -- 自动攻击倒计时
        grid:updateCountDown();

        -- 出手
        -- GhostShipM.doAttack();

        -- EventMgr.fire(event.PLAYER_MOVE);
    end);

    -- 关注幽灵船攻击结束的事件
    EventMgr.register("UIDungeonMain", event.SHIP_CAST_OVER, function()
        local ship = ME.user.ship;
        local pos = ship:getPos();
        local grid = self.grids[pos];

        -- 施法完毕，切换至待机状态
        trace("UIDungeonMain", "幽灵船施法完毕，切换至待机状态。");
        grid:switchShipAction(SHIP_ACTION_IDLE);

        -- 自动攻击倒计时
        grid:updateCountDown();

        -- 触发出手事件
        EventMgr.fire(event.PLAYER_MOVE, pos);
    end);

    -- 关注收集能量的事件
    EventMgr.register("UIDungeonMain", event.COLLECT_ENERGY, function(args)
        -- 需要播放特效，那么抛出事件
        if args["need_effect"] then
            local function callBack()
                self:whenCollectEnergy(args);
            end
            EventMgr.fire(event.BOSS_RECIEVE_BUFF, { ["skillId"] = "default", ["pos"] = args["pos"], ["callBack"] = callBack, });
        else
            self:whenCollectEnergy(args);
        end
    end);

    -- 关注消耗能量的事件
    EventMgr.register("UIDungeonMain", event.COST_ENERGY, function(args)
        self:whenCostEnergy(args);
    end);

    -- 关注添加场景行为的事件
    EventMgr.register("UIDungeonMain", event.ADD_SCENE_SEQUENCE, function(args)
        self:whenAddSceneSequence(args);
    end);

    -- 关注自动显示怪物信息界面
     EventMgr.register("UIDungeonMain", event.MONSTER_INFO_AUTO_APPEAR, function(args)
          if args.isBoss == true then
             local uiBossInfo = UIBossMonsterInfo.create(args.id, args.monster, args);
             UIMgr.getCurrentScene():addForm(uiBossInfo);
             -- 音效
             AudioM.playFx("ui_open1");
         else
             UIMgr.getCurrentScene():removeFormByName("UIMonsterInfo");
             local uiInfo = UIMonsterInfo.create(args.id, args.monster, args);
             UIMgr.getCurrentScene():addForm(uiInfo);
             -- 音效
             AudioM.playFx("ui_open1");
         end
     end);

    -- 关注拆分装备事件
    EventMgr.register("UIDungeonMain", event.SPLIT_EQUIP, function(args)
       -- 刷新相关属性
       self:updateUI();
    end);

    -- 关注玩家进入夹层迷宫的事件
    EventMgr.register("UIDungeonMain", event.ENTER_UNREAL_DUNGEON, function(args)
        local unrealId = args.unrealId;
        -- SLIMEC-2142 此处不触发
        --[[
        local dbase = UnrealDungeonM.query(unrealId, "dbase");
        if type(dbase) == "table" and type(dbase["random_talk_grids"]) == 'table' then
            -- 触发随机发言
            local randPosList = dbase["random_talk_grids"];
            self:doRandTalk(randPosList);
        end--]]


        if self.unrealId then
            -- 已经是夹层界面
            return;
        end

        local pos = args.pos;
        if type(pos) == "number" then
            local grid = self.grids[pos];
            grid:onEnterUnreal();
        end
    end);

    -- 关注通过与冈布奥对话获得奖励的事件
    EventMgr.register("UIDungeonMain", event.GAIN_ITEM_BY_TALK, function(args)
        local bonus = args.bonus;
        local msg   = args.msg;
        playGainItemByHeroSpeak(bonus, msg);
    end);

    -- 关注关闭广告屏幕的事件
    EventMgr.register("UIDungeonMain", event.DISMISS_AD_SCREEN, function()
        local pos = AdM.getTvPos();
        if type(pos) == "number" then
            local grid = self.grids[pos];
            grid:onDismissAdScreen();
        end
    end)

    -- 关注收看广告的事件
    EventMgr.register("UIDungeonMain", event.WATCH_TV_BONUS, function()
        alert(getLocStr("tv_bonus_tip"));
    end)

    -- 注册奇怪的祭坛激活的事件
    EventMgr.register("UIDungeonMain", event.ODD_ALTAR_ACTIVE, function(args)
        local pos =  args.pos;
        local grid = self.grids[pos];

        grid:onOddAtarActive();
    end);

    -- 注册小妖精被放走的事件
    EventMgr.register("UIDungeonMain", event.RESCUE_PIXY, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];
        grid:onRescuePixy();
    end);

    -- 注册往妖精之井投放金币的事件
    EventMgr.register("UIDungeonMain", event.PIXY_THROW_COIN, function(args)
        local pos         = args.pos;
        local bonus       = args.bonus;
        local optionIndex = args.optionIndex;
        local grid = self.grids[pos];
        grid:onThrowCoinToWell(bonus, optionIndex);
    end);

    -- 告警状态改变
    EventMgr.register("UIDungeonMain", event.ALERT_STATE_CHAGNED, function(state)
        -- 显示当前告警状态
        self:showAlertState(state);
    end);

    -- 关注自动战斗拾取世界树之枝事件
    EventMgr.register("UIDungeonMain", event.AUTO_PICK_FEATUREITEM, function(params)
        self.grids[params.pos]:onPickupFeatureItem();
    end);

    -- 注册获得焦点的回调
    EventMgr.register("UIDungeonMain", event.FOCUS_CHANGED, function(para)
        if para["get"] == "UIDungeonMain"
         or para["get"] == "UIDungeonHpAlert"
         or para["get"] == "UIStoneAlert"
         or para["get"] == "UICommonAlert" then
            -- 获得焦点时，显示上下底菜单
            -- 当处于禁魔状态或残血状态时，UIDungeonHpAlert和UIStoneAlert获得焦点相当于UIDungeonMain获得焦点
            self:showMenu();

            -- 如果当前有天空战扣除的血量，那么去执行扣除操作
            if self.skyCombatCostHp then
                self:doSkyCombatCostHp();
            end

            -- 如果地牢播放序列当前是暂停的，那么恢复播放
            if self.dungeonActionManager:isPause() then
                self.dungeonActionManager:continue();
            end

            -- 检查下预警效果
            self:checkHpAlert();
            self:checkGasAlert();
        end
    end);

    -- 注册合成提醒事件的回调
    EventMgr.register("UIDungeonMain", event.COMPOUND_REMIND, function(para)
       local rule = para.ruleId;
       self:whenCompoundRemind(rule);
    end);

    -- 创世之门奖励
    EventMgr.register("UIDungeonMain", event.CREATION_GATE_BONUS, function(params)
        local grid = self.grids[params.pos];
        performWithDelay(self.node, function ()
            grid:playBonusListEffect(params.bonusList, false, nil, nil, params.curNum);
            self:updateUI();
        end, 0.5 * params.index);
    end);

    -- 创世之门光效
    EventMgr.register("UIDungeonMain", event.CG_KEY_SUBMITED_EFFECT, function(params)
        local grid = self.grids[params.pos];
        local effectNode = findChildByName(grid.node , "effect_node");

        if effectNode then
            effectNode:setVisible(true);
        end

        playEffect(effectNode, 1264, 82, 190, nil, 1.3);
        playEffect(grid, 1263, -20, 110, nil, 1.3);
    end);

    -- 注册合成成功的事件回调
    EventMgr.register("UIDungeonMain", event.UI_COMPOUND_DONE, function(args)
        if UnrealDungeonM.isInUnreal() then
            -- 如果在夹层中，检查传送门显示
            local unrealId = UnrealDungeonM.getUnrealId();
            local dbase = UnrealDungeonM.query(unrealId, "dbase");
            local doorGrid = self.grids[self.doorIndex];
            if doorGrid.item:isVisible() == false and dbase["door_appear_by_compound"] == 1 then
                local function doorAppear()
                    local exitNode = findChildByName(self.node, "CT1/cave_mask/exit_node");
                    playBuildingAppearEffect(nil, exitNode);
                    local exitImg = findChildByName(exitNode, "exit");
                    portalEffect(exitImg, 60, 66);
                end
                performWithDelay(self, doorAppear, 0.5);
            end
        end
    end);

    -- 注册德古拉的展柜解锁的事件回调
    EventMgr.register("UIDungeonMain", event.UNLOCK_SHOWCASE, function(args)
        local pos = args.pos;
        local grid  = self.grids[pos];
        grid:onShowcaseUnlock();
    end);

    -- 注册领取圣棺奖励的回调
    EventMgr.register("UIDungeonMain", event.TAKE_COFFIN_BONUS, function(params)
        local bonus = params.bonus;
        local pos   = params.pos;
        self.grids[pos]:onTakeCoffinBonus(bonus);
    end);

    -- 关注圣器属性升级的事件
    EventMgr.register("UIDungeonMain", event.HOLY_ITEMS_IMPROVE, function(args)
        -- 属性可能提升了，刷新一下界面
        self:updateUI();
    end);

    -- 关注消息提醒出现事件
    EventMgr.register("UIDungeonMain", event.MSG_REMINDER_APPEAR, function()
        for _, bar in pairs(self.skillEnergyBarList) do
            bar:setAlpha(77);
        end
    end);

    -- 关注消息提醒事件
    EventMgr.register("UIDungeonMain", event.MSG_REMINDER_DISAPPEAR, function()
        for _, bar in pairs(self.skillEnergyBarList) do
            bar:setAlpha(255);
        end
    end);

    -- 关注特技触发事件
    -- 需要播放配置专属技光效的话，需要传入对应格子的pos,否则播放虚影效果
    EventMgr.register("UIDungeonMain", event.SPECIAL_SKILL_TRIGGER, function(args)
        local propId = args.propId;
        local propKey = args.key;
        local pos = args.pos;

        -- 获取对应宠物
        local pets = SpecialSkillInfoM.getSpecialTriggerPets(propId, propKey)
        for _, petId in pairs(pets) do
            -- 播放特技发动效果
            local skillId = PetM.query(petId, "special");
            playSpecialSkillTriggerEffect(skillId, petId, propId, pos);
        end
    end);

    -- 关注角斗台观众发言的事件
    EventMgr.register("UIDungeonMain", event.AUDIENCE_TALK, function(args)
        local pos     = args.pos;
        local content = args.content;
        local delay   = args.delay;
        local talkType = args.talkType;

        local grid = self.grids[pos];
        if not grid then
            return;
        end
        grid:doRoleDelayTalk2(content, delay, talkType);
    end);

    -- 关注玩家进入角斗台的事件
    EventMgr.register("UIDungeonMain", event.ENTER_ARENA, function(args)
        local enterType = args.enterType;

        -- TODO 根据进入时的情形播放入场音效
        trace("UIDungeonMain", "玩家进入角斗台，enterType = %s", enterType);
    end);

    -- 关注角斗台玩家胜利的事件
    EventMgr.register("UIDungeonMain", event.ARENA_WIN, function(args)
        -- TODO 播放全场欢呼+掌声

        -- 播放胜利光效
        UIMgr.getCurrentScene():removeFormByName("UIImproveNotify");
        local uiNotify = UIImproveNotify.create(getLocStr("arena_win_title"), "");
        UIMgr.getCurrentScene():addForm(uiNotify);

        -- 先停止背景音乐，播放胜利音效
        AudioM.stopPlayBgMusic(true);
        AudioM.playFx("arena_winning");

        -- 切换背景音乐
        performWithDelay(self, function() AudioM.playBgMusic("bgm1"); end, 1.5);
    end);

    -- 关注玩家角斗台中死亡的事件
    EventMgr.register("UIDungeonMain", event.ARENA_USER_DIE, function()
        -- TODO 播放全场嘘声
    end);

    -- 关注东方神龛提交事件
    EventMgr.register("UIDungeonMain", event.EAST_SHRINE_SUBMIT, function(args)
        local pos     = args.pos;
        local classId = args.classId;
        local bonus   = args.bonus;
        local isSucc  = args.isSucc;

        local grid = self.grids[pos];
        if not grid then
            return;
        end

        grid:onEastShrineSubmit(classId, bonus, isSucc);
    end);

    -- 关注机械祭坛提交事件
    EventMgr.register("UIDungeonMain", event.MACHINE_ALTAR_SUBMIT, function(args)
        local pos     = args.pos;
        local classId = args.classId;
        local bonus   = args.bonus;
        local isSucc  = args.isSucc;

        local grid = self.grids[pos];
        if not grid then
            return;
        end

        grid:onMachineAltarSubmit(classId, bonus, isSucc);
    end);

    -- 关注通用提交建筑提交事件
    EventMgr.register("UIDungeonMain", event.NORMAL_SUBMIT, function(args)
        local pos     = args.pos;
        local classId = args.classId;
        local bonus   = args.bonus;
        local isSucc  = args.isSucc;

        local grid = self.grids[pos];
        if not grid then
            return;
        end

        grid:onNormalSubmit(classId, bonus, isSucc);
    end);

    -- 关注古老的保险箱奖励事件
    EventMgr.register("UIDungeonMain", event.OLD_SAFE_BOX_BONUS, function(params)
            performWithDelay(self.node, function ()
                self.grids[params.pos]:playBonusListEffect(params.bonus, params.isDisappear, nil, nil, params.curNum);
            end, 0.5 * (params.index - 1));
    end);

    -- 关注乌龟大师奖励事件
    EventMgr.register("UIDungeonMain", event.MASTER_OOGWAY_SUBMIT, function(args)
        local pos     = args.pos;
        local classId = args.classId;
        local bonus   = args.bonus;
        local isSucc  = args.isSucc;

        local grid = self.grids[pos];
        if not grid then
            return;
        end

        grid:onMasterOogwaySubmit(classId, bonus, isSucc);
    end);

    -- 关注播放建筑强光的事件
    EventMgr.register("UIDungeonMain", event.BUILDING_STRONG_LIGHT, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];
        if not grid then
            return;
        end

        local isWeak = args.isWeak or false;

        playBuildingStrongLight(grid, isWeak);
    end);

    -- 关注树屋攻击事件
    EventMgr.register("UIDungeonMain", event.TREE_HOUSE_ATTACK, function(args)
        local result    = args.result;
        local skillId   = args.skillId;
        local pos       = args.pos;
        local target    = args.target;
        local grid      = self.grids[pos];
        local hasRemote = args.hasRemote;

        if not grid then
            return;
        end

        local function doTreeHouseAttack()
            local duration  = 0;
            if not result and hasRemote then
                -- 远程怪无法攻击发言
                grid:treeHouseTalk(skillId, {["hasRemote"] = hasRemote});
            end

            if result then
                -- 树屋发言，特殊回合发言的不处理
                grid:treeHouseTalk(skillId);
                -- 攻击效果
                grid:onMoneyAttack(skillId);
            end

            grid:updateCountDown();
            return 0.2;
        end

        if result and target then
            local targetPos = target:getPos();
            local targetNode = self.grids[targetPos];

            targetNode.actionManager:addAction(targetNode.gridData, doTreeHouseAttack, "doTreeHouseAttack", {}, GRID_ACTION_PHASE_THROW_PREPARE);
        else
            doTreeHouseAttack();
        end
    end);

    -- 关注特殊树屋发言事件
    EventMgr.register("UIDungeonMain", event.TREE_HOUSE_TALK, function(args)
        local skillId = args.skillId;
        local pos     = args.pos;
        local grid    = self.grids[pos];

        if not grid then
            return;
        end
        grid:treeHouseTalk(skillId);
    end);

    -- 关注树屋激活事件
    EventMgr.register("UIDungeonMain", event.ACTIVE_TREE_HOUSE, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];
        if not grid then
            return;
        end

        -- 更新倒计时
        grid:updateCountDown();
        -- 播放强光
        playBuildingStrongLight(grid, false);

        -- 猴子待机
        grid:onTreeHouseActive();
    end);

    -- 关注施放大预言术
    EventMgr.register("UIDungeonMain", event.DRAGON_PREDICTION, function(args)
        -- 锁屏一段时间
        local lockPanel2 = findChildByName(self.node, "lock_panel2");
        lockPanel2:setVisible(true);

        local function cast()
            DungeonActionM.go("cast_pre_skill", args.pos, args.skill_id);

            EventMgr.fire(event.PLAYER_MOVE, args.pos);

            self:checkTimeCeaseEffect();

            if args.last then
                -- 解除锁屏
                local lockPanel2 = findChildByName(self.node, "lock_panel2");
                lockPanel2:setVisible(false);
            end
        end

        local function doTip()
            local id = DragonPredictionM.getIdBySkillId(args.skill_id);
            local desc = DragonPredictionM.query(id, "desc");
            alert(desc, nil, 0.8);
        end

        local function playList()
            playPredictionEffect();

            performWithDelay(self, doTip, 0.3);

            performWithDelay(self, cast, 2.2);
        end

        local delay = 3.3 * (args.index - 1);
        performWithDelay(self, playList, delay);
    end);

    -- 施放大预言术完成
    EventMgr.register("UIDungeonMain", event.CAST_PRE_OVER, function(args)
        -- 解除锁屏
        local lockPanelNode = self:getLockPanelParent();
        lockPanelNode:setVisible(false);
    end);

    -- 关注翻转灵魂的事件
    EventMgr.register("UIDungeonMain", event.RUMMAGE_SOUL, function(args)
        local bonus = args.bonus;
        local pos = args.pos;
        local grid = self.grids[pos];
        if not grid then
            return;
        end

        -- 播放奖励飞入动画
        local function callback()
            grid:playBonusListEffect(bonus);
        end
        performWithDelay(grid, callback, 0.5);
    end);

    -- 关注发现隐藏物件的事件
    EventMgr.register("UIDungeonMain", event.FIND_HIDE_ITEM, function()
        self:handleCave();
    end);

    -- 关注最后一层释放魔法扫帚事件
    EventMgr.register("UIDungeonMain", event.MAX_FLOOR_ALREADY, function(args)
        local str = getLocStr("max_floor_already");
        alert(str);
    end);

    -- 关注提交小徽章的事件
    EventMgr.register("UIDungeonMain", event.SUBMIT_SMALL_BADGE, function(args)
        local pos = args.pos;
        local bonus = args.bonus;
        local grid = self.grids[pos];
        grid:onBraveBuildingBonus(bonus, false);
    end);

    -- 关注公主出现的事件
    EventMgr.register("UIDungeonMain", event.PRINCESS_APPEARED, function(args)
        local pos = args.princessPos;
        local grid = self.grids[pos];
        grid:onPrincessAppeared();
    end);

    -- 关注勇者斗恶龙建筑奖励事件
    EventMgr.register("UIDungeonMain", event.BRAVE_BUILDING_BONUS, function(args)
        local pos        = args.pos;
        local bonus      = args.bonus;
        local remainGrid = args.remainGrid;
        local grid       = self.grids[pos];
        local isDisappear = not remainGrid;
        grid:onBraveBuildingBonus(bonus, isDisappear);
    end);

    -- 关注兑换获得奖励的事件
    EventMgr.register("UIDungeonMain", event.EXCHANGE_BONUS, function(args)
        local pos   = args.pos;
        local bonus = args.bonus;
        local grid  = self.grids[pos];
        local isDisappear = false;
        grid:onBraveBuildingBonus(bonus, isDisappear);
    end);

    -- 关注侍卫队长奖励事件
    EventMgr.register("UIDungeonMain", event.GRUARDS_LEADER_BONUS, function(args)
        local pos   = args.pos;
        local bonus = args.bonus;
        local isDisappear = args.isDisappear;
        local grid  = self.grids[pos];
        grid:onBraveBuildingBonus(bonus, isDisappear);
    end);

    -- 关注洞窟被点亮的事件
    EventMgr.register("UIDungeonMain", event.CAVE_LIGHT_UP, function()
        self:handleCave();

        -- 刷新传送门
        local doorGrid = self.grids[self.doorIndex];
        doorGrid:initDoor();
    end);

    -- 关注解救公主的事件
    EventMgr.register("UIDungeonMain", event.RESCUE_PRINCESS, function()
        -- 刷新传送门
        local doorGrid = self.grids[self.doorIndex];
        doorGrid:initDoor();
    end);

    -- 关注旅者帐篷事件
    EventMgr.register("UIDungeonMain", event.TRAVELLER_TENT, function(args)
        local pos = args.pos;
        local hp  = args.hp;
        local mp  = args.mp;
        local grid = self.grids[pos];
        grid:onTravellerTentEvent(hp, mp);
    end)

    -- 关注神官召唤怪物的事件
    EventMgr.register("UIDungeonMain", event.PRIEST_SUMMON, function(args)
        local pos = args.pos;
        local gridData = DungeonM.getGridByPos(pos);
        if gridData.monster then
            local msg = string.format(getLocStr("monster_appear"), gridData.monster:getName());
            alert(msg);
        end
    end)

    -- 关注保存勇者斗恶龙副本进度的事件
    EventMgr.register("UIDungeonMain", event.SAVE_BRAVE_DRAGON, function(args)
        local result = args.result;

        if result ~= 1 then
            alert(getLocStr("save_progress_fail"));
            closeCommunicatingForm();
            return;
        end

        if UnrealDungeonM.isInUnreal() then
            -- 已经在夹层中了
            closeCommunicatingForm();
            return;
        end

        local function startEnter()
            closeCommunicatingForm();
            alert(getLocStr("save_progress_succ"));

            local function doEnter()
                -- 进入龙王城
                local pos = BraveVSDragonM.queryMixed("dragon_city_pos");
                local grid = DungeonM.getGridByPos(pos);
                local unrealId = grid.unrealId;
                DungeonActionM.go("start_unreal", pos, unrealId);
            end
            performWithDelay(self, doEnter, 1.0);
        end

        performWithDelay(self, startEnter, 1.0);
    end);

    -- 关注岩浆障碍被消除的事件
    EventMgr.register("UIDungeonMain", event.REMOVE_MAGMA_OBSTACLE, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];

        -- 播放岩浆障碍消除的特效
        playMagmaObstacleEffect(grid);

        local function callback()
            -- 建筑消失
            playBuildingDisappear(grid);
        end

        if FeatureItemM.isFeatureOpen("awake") then
            performWithDelay(grid, callback, 0.8);
        end
    end);

    -- 元素转换
    EventMgr.register("UIDungeonMain", event.TRANS_ELIMINATE_CHANGE, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];
        grid:onTransEliminate();
    end);

    -- 元素爆炸
    EventMgr.register("UIDungeonMain", event.TRANS_ELIMINATE_PASSED, function(args)
        local grids = args.grids;
        for _, pos in pairs(grids) do
            local grid = self.grids[pos];
            -- 爆炸
            local function doBomEffect()
                -- 爆炸光效
                playEffect(grid, 1233, 0, 20);
                grid.item:removeAllChildren();
                grid.item.transDisplay = nil;
                return 0.5;
            end
            grid.actionManager:addAction(grid.gridData, function() return 1; end, "wait", {}, GRID_ACTION_PHASE_GRID_OPENED);
            grid.actionManager:addAction(grid.gridData, doBomEffect, "doBomEffect", {}, GRID_ACTION_PHASE_GRID_OPENED);
            grid.actionManager:addAction(grid.gridData, function() return 0.5; end, "wait", {}, GRID_ACTION_PHASE_GRID_OPENED);
        end

        -- 开门
        local function callback()
            self.grids[self.doorIndex]:openEntry();
        end

        -- 延迟下播放光效
        performWithDelay(self, callback, 1.5);
    end);

    -- 暗之石柱爆炸
    EventMgr.register("UIDungeonMain", event.DARK_STONE_BLAST, function(args)
        local pos = args["pos"];
        local grid = self.grids[pos];

        local function doBomEffect()
            -- 爆炸光效
            return playDarkStoneBlast(grid);
        end
        grid.actionManager:addAction(grid.gridData, doBomEffect, "doBomEffect", {}, GRID_ACTION_PHASE_MONSTER_DAMAGED);
    end);

    -- 石柱解除
    EventMgr.register("UIDungeonMain", event.DARK_STONE_DISABLE, function(args)
        local pos = args["pos"];
        local grid = self.grids[pos];
        -- 播放解除效果
        grid:onStoneDisable();
        -- 建筑消失
        local function delay()
            playBuildingDisappear(grid);
            local cdNode = findChildByName(grid.node, "cd_node");
            cdNode:setVisible(false);
        end
        performWithDelay(self.node, delay, 1);
    end);

    -- 关注建筑刷怪事件
    EventMgr.register("UIDungeonMain", event.MONSTER_BUILDING_REFRESH, function(args)
        local pos   = args.pos;
        local class = args.class;
        local tip = MonsterBuildingM.queryRefreshTip(class, pos);
        if not tip then
            return;
        end
        alert(tip);
    end);

    -- 关注龙王城宝箱伤害事件
    EventMgr.register("UIDungeonMain", event.DRAGON_KING_BOX_DAMAGE, function(args)
        local damage = args.damage;
        self:playHeroDeadlyDamaged(damage);
    end);

    -- 关注阿瓦隆巨炮被摧毁事件
    EventMgr.register("UIDungeonMain", event.AVALON_CANNON_DESTORY, function(args)
        local pos = args["pos"];
        local uiGrid = self.grids[pos];

        if uiGrid then
            performWithDelay(UIMgr.getCurrentScene(), function()
                uiGrid:cannonDestory();
            end, 1);
        end
    end);

    -- 关注定时炸弹拆除事件
    EventMgr.register("UIDungeonMain", event.REMOVE_TIME_BOMB, function(args)
        local pos = args["pos"];
        local uiGrid = self.grids[pos];

        if uiGrid then
            uiGrid:removeTimeBomb(args);
        end
    end);

    -- 界面析构后，需要清理下
    self:registerScriptHandler(function(ev)
        if ev == "exitTransitionStart" then

        elseif ev == "exit" then
            cclog("UIDungeonMain界面析构清理");

            -- 移除所有事件回调
            EventMgr.removeAll("UIDungeonMain");

            -- 停止所有预警特效
            self:stopAllAlertEffect();

            -- 将技能界面和装备干掉（如果玩家不在地牢中的话）
            local function releaseUIs()
                local removeUIs = {"UIMagicScrolls", "UIEquips", "UIHeroAttrib", "UISystemSetting"};
                for _, formName in ipairs(removeUIs) do
                    if UIMgr.getCurrentScene():isOpen(formName) then
                        UIMgr.getCurrentScene():removeFormByName(formName);
                    end
                end

                if not DungeonM.isInDungeon() then
                    if magicScrollUI then
                        magicScrollUI:resetIndex();
                        if magicScrollUI:isExisted() then print("释放卷轴窗口"); magicScrollUI:release(); end
                    end
                    magicScrollUI = nil;

                    if equipsUI ~= nil and equipsUI:isExisted() then print("释放宝物窗口"); equipsUI:release() end
                    equipsUI = nil;
                end
            end
            performWithDelay(UIMgr.getCurrentScene(), releaseUIs, 0.01)

            if DungeonM.getState() == DUNGEON_LEAVE or DungeonM.hasContext() then
                if UIMgr.getCurrentScene().mainMap then
                    -- 如果是天空副本，那么回到飞艇界面
                    if DungeonM.dungeonType and DungeonM.dungeonType == DUNGEON_TYPE_SKY_INSTANCE then
                        require "game/ui/form/sky_city/UISkyCity"
                        local uiForm = UISkyCity.create();
                        UIMgr.getCurrentScene():addForm(uiForm);
                        AudioM.playFx("ui_open1");
                    elseif DungeonM.dungeonType and DungeonM.dungeonType == DUNGEON_TYPE_SKY_ACTIVITY_INSTANCE then
                        -- 天空活动副本，则打开天空之城界面和天空活动界面
                        require "game/ui/form/sky_city/UISkyCity"
                        local uiForm = UISkyCity.create();
                        UIMgr.getCurrentScene():addForm(uiForm);

                        require "game/ui/form/sky_city/UISkyActivity"
                        local uiSkyActivity = UISkyActivity.create(DungeonM.SkyActivityId);
                        UIMgr.getCurrentScene():addForm(uiSkyActivity);
                        AudioM.playFx("ui_open1");
                    elseif DungeonM.dungeonType and DungeonM.dungeonType == DUNGEON_TYPE_AWAKE then
                        -- 觉醒试炼地牢，打开觉醒试炼地牢界面
                        require "game/ui/form/awake/UIAwakeMain"
                        local uiForm = UIAwakeMain.create();
                        UIMgr.getCurrentScene():addForm(uiForm);

                        AudioM.playFx("ui_open1");
                    else
                        -- 否则回到世界地图
                        UIMgr.getCurrentScene().mainMap:show();
                        UIMgr.getCurrentScene().mainMap:reloadImages();

                        -- 播放主地图背景音乐
                        local willEnterInstance = DungeonInstanceM.isInInstance() or UnrealDungeonM.isInUnreal();
                        if not self.isDungeonInstance and not willEnterInstance then
                            -- 本身不是洞窟类迷宫，并且不是将要进入洞窟类迷宫，再播放大地图音乐
                            local bgm = AudioM.queryFormAudioInfo("UIMainMap", "open_bgm");
                            AudioM.playBgMusic(bgm);
                        end
                    end
                end

                if UIMgr.getCurrentScene().topMenu then
                    UIMgr.getCurrentScene().topMenu:setVisible(true);
                end

                if UIMgr.getCurrentScene().bottomMenu then
                    UIMgr.getCurrentScene().bottomMenu:setVisible(true);
                end
            end

            -- 重置当前层的龙珠数量
            DragonWishM.setLayerCount(0);

            -- 如果正在播放遗产动画，则强制终止
            if DungeonLegacyM.isPlayingLegacy() then
                DungeonLegacyM.setPlayingLegacy(false);
                closeFormByName("UIBonusNotify");
                closeFormByName("UIInvisibleForm");
            end

            if LUA_COVERAGE ~= 1 then
                -- 关闭定时GC
                GCM.stop();

                -- 启用自动GC
                enableGC();
            end
        elseif ev == "enter" then
            -- 在地牢时主界面隐藏，减少渲染消耗
            if UIMgr.getCurrentScene().mainMap:isVisible() then
                UIMgr.getCurrentScene().mainMap:releaseImages();
                UIMgr.getCurrentScene().mainMap:hide();
                UIMgr.getCurrentScene().topMenu:setVisible(false);
                UIMgr.getCurrentScene().bottomMenu:setVisible(false);
            end

            -- 清理无用纹理
            UIMgr.clearUnusedTextures(true);

            -- 会将level合图中不用的spriteFrame清理掉，需要重新加载下
            cc.SpriteFrameCache:getInstance():addSpriteFrames("images/ui/level.plist");

            -- 关闭自动GC
            if LUA_COVERAGE ~= 1 then
                disableGC();

                -- 启动定时GC
                GCM.start();
            end

            self:updateUI();

            hideLoadingForm();

            -- 播放战斗音乐
            local dungeonId = DungeonM.getDungeonId();
            local layer     = DungeonM.currentLayer();
            local bgm = DungeonAreaM.query(dungeonId, "bg_music");

            if DungeonBossM.isBossLayer(dungeonId, layer) then
                bgm = "bgm_boss_layer";
            end

            -- 抛出UI层开格子事件
            self:uiGridOpenEvent();

            -- 迷宫告警状态初始化
            AlertStateM.init();

            -- 检查血量预警
            self:checkHpAlert();

            -- 处理进入下一层时的属性增加效果
            self:handleNextFloorGainBonus({["arrange"] = 1});

            -- 自销毁装备奖励弹框
            self:playSelfDestructEquipEffect();

            -- 初始化新手关卡引导
            self:initGuide();

            -- 初始化怪物BUFF光效模块
            self:initMonsterBuffEffect();

            -- 注册back按钮的处理函数
            self:initBackEventHandler();

            -- 处理怪物洞穴
            self:handleCave();

            self:whenNextFloor();

            -- 处理BUFF状态
            performWithDelay(self, function() self.dungeonStatusBar:updateBar(); end, 0.01);

            -- 检查时间停止效果
            self:checkTimeCeaseEffect();

            -- 检查变身效果
            self:checkShapeChangeEffect();

            -- 检查石化效果
            self:checkIsInStone();

            -- 检查下boss层流浪者的数量
            self:checkTrampInBossLayer();

            -- 一些入场的特殊显示
            self:specialEnterEffect();

            -- 检查电流装置
            self:checkInterfereDevice();

            -- 检查需要隐藏的一些界面
            self:checkNeedHideUI();

            -- 神龙夹层
            if UnrealDungeonM.isInUnreal() then
                -- 神龙夹层背景音乐转换
                bgm = "bgm4";

                -- 竞技场
                if ArenaM.isInArena() then
                    bgm = "bgm_arena";
                end

                local unrealId = UnrealDungeonM.getUnrealId();

                -- 进入提示
                local tip = UnrealDungeonM.query(unrealId, "enter_tip");
                if type(tip) == "string" and tip ~= "" then
                    performWithDelay(self, function() alert(tip); end, 0.3);
                end

                -- 如果需要播放开场光效
                local dbase = UnrealDungeonM.query(unrealId, "dbase") or {};
                if not dbase["ignore_enter_effect"] then
                    -- 等播完全屏光效后在显示格子
                    local mask = findChildByName(self.node, "black_mask");
                    -- 出现振动时会穿帮，放大蒙版
                    local scaleX = mask:getScaleX()*1.2;
                    local scaleY = mask:getScaleY()*1.2;
                    mask:setScaleX(scaleX);
                    mask:setScaleY(scaleY);

                    mask:setVisible(true);

                    local function callback()
                        local fadeOut = cc.FadeOut:create(0.4);
                        local callFunc = cc.CallFunc:create(function()
                            mask:setVisible(false);
                        end);
                        mask:runAction(cc.Sequence:create(fadeOut, callFunc));
                    end
                    performWithDelay(self, callback, 0.8);

                    -- 播放传送光效
                    playDragonDeliverEffect();
                end
            end

            -- 标记洞窟、夹层类副本
            if DungeonInstanceM.isInInstance() or UnrealDungeonM.isInUnreal() then
                self.isDungeonInstance = true;
            end

            -- 播放背景音乐
            AudioM.playBgMusic(bgm);

            local function showReviveForm()
                -- 显示复活界面
                if UIMgr.getCurrentScene():isOpen("UIDungeonRevive") then
                    UIMgr.getCurrentScene():removeFormByName("UIDungeonRevive");
                end

                local uiDungeonRevive = UIDungeonRevive.create();
                UIMgr.getCurrentScene():addForm(uiDungeonRevive);
            end

            -- 如果已经死亡了(有可能界面被压在下面了)
            if ME.user:isDead() then
                performWithDelay(self, showReviveForm, 0.05);
            end
        end
    end);

    -- 关注设置排行榜概要事件
    EventMgr.register("UIDungeonMain", event.SET_RANK_BRIEF, function(args)
        local result = args.result;

        -- 昵称已存在
        if result ~= "ok" then
            return;
        end

        local bonus = FormulaM.invoke("CALA_SET_NICK_BONUS");
        for i = 1, GRID_ROW_NUM do
            for j = 1, GRID_COL_NUM do
                local index = (i - 1) * GRID_COL_NUM + j;

                local grid = self.grids[index];
                if grid.gridData.type == GRID_TYPE_TOPLIST then
                    grid:onToplistBuildingDispear(bonus);
                end
            end
        end
    end);

    -- 佐罗的面罩升级
    EventMgr.register("UIDungeonMain", event.ZOORO_UPGRADE_TIP, function(args)
        local classId = args.classId;
        local monsterName = args.monsterName;
        -- 弹框提示
        showBonusNotifyForm(getLocStr("zooro_mask_title"), {{1, classId, 1},}, nil,
            string.format(getLocStr("zorro_mask_tip"), monsterName), getLocStr("btn_text_ok"));
    end);

    -- 关注海格力斯发言事件
    EventMgr.register("UIDungeonMain", event.HERACLES_TALK, function(args)
        local transTimes = args["trans_times"];
        local dialog = FormulaM.invoke("CALC_HERACLES_TALK", transTimes);
        if dialog then
            startDialogueTemplate(dialog, nil);
        end

        -- 重置
        ME.user.dbase:setTemp("hero_talk", nil);
    end);

    -- 关注出战宠物变身事件
    EventMgr.register("UIDungeonMain", event.SHAPE_CHANGE, function(args)
        local shapeId   = args["shape_id"];
        local condition = args["condition"];

        -- 已经变身,并且不是处于多次变身
        if ShapeChangeM.getInShapeChange() and
            condition ~= ShapeChangeM.CHANGE_REPEATEDLY and
            condition ~= ShapeChangeM.REPEATEDLY_LATER then
            return;
        end

        -- 变身光效
        self:playActivePetShapeChangeEffect(shapeId, true);

        -- 标记下
        ShapeChangeM.setInShapeChange(true);
    end);

    -- 关注出战宠物变身还原事件
    EventMgr.register("UIDungeonMain", event.SHAPE_RESTORE, function(args)
        -- 已经是原模型
        if not ShapeChangeM.getInShapeChange() then
            ShapeChangeM.setInShapeChange(false);
            return;
        end

        -- 光效
        local shapeId = args["shape_id"];
        local isShowEffect = args["show_effect"];
        self:shapeRestore(shapeId, isShowEffect);

        -- 标记下
        ShapeChangeM.setInShapeChange(false);
    end);

    --关注激活图纸事件
    EventMgr.register("UIDungeonMain", event.ACTIVE_DRAWING, function(args)
        -- body
        local classId = args.classId;
        local delay  = args.delay;
        local tip = getLocStr("you_active_drawing");
        local name = ItemM.query(classId, "name");
        local msg = string.format(tip, name);

        local function doTip()
            alert(msg);
        end

        performWithDelay(self, doTip, 1.5);
    end);

    --关注随机奖励事件
    EventMgr.register("UIDungeonMain", event.GAIN_RANDOM_BONUS, function(args)
        local bonus = args["bonus"];

        -- 如果是龙珠，那么播放特效
        if bonus[2] == 1500 then
            local modelNode = findChildByName(self.node, "CT2/model_node");
            gainDragonBallEffect(modelNode, bonus[3]);
        end
    end);

    -- 关注拾取龙珠事件
    EventMgr.register("UIDungeonMain", event.GAIN_DRAGON_BALL, function(args)
        local num = args["num"];
        local modelNode = findChildByName(self.node, "CT2/model_node");

        gainDragonBallEffect(modelNode, num);
    end);

    -- 关注学习龙语魔法
    EventMgr.register("UIDungeonMain", event.LEARN_DRAGON_MAGIC, function(args)
        local skillEnergyBarNode = findChildByName(self.node, "CT1/skill_energy_bar");
        skillEnergyBarNode:removeAllChildren();
        self:initSkillEnergyBar();

        local skillIds = SlimeSkillM.getAllDragonMagic();

        local index = 1;
        local function playAnim()
            local skillId = skillIds[index];
            local icon = FormulaM.invoke("GET_DRAGON_MAGIC_ICON", skillId);
            local iconPath = getItemIconPath(icon);
            local skillName = SkillM.query(skillId, "name");
            local grid = UIDungeonMgr.getCurLevel():getAllGrids()[25];

            pickupSkillEffect(grid, UIDungeonMgr.getCurLevel():getSpellNode(), iconPath, skillName);
            index = index + 1;
        end

        for i = 1, #skillIds do
            local delay = i * 0.15;
            performWithDelay(self, playAnim, delay);
        end
    end);

    -- 关注使用道具进入迷宫事件
    EventMgr.register("UIDungeonMain", event.USE_PROP_ENTER_INSTANCE, function(args)
    end);


    --关注激活水晶结界事件
    EventMgr.register("UIDungeonMain", event.ACTIVE_CRYSTAL_BAR, function(args)
        local pos = args.pos;
        self.grids[pos]:onCrystalActive();
    end);

    --水晶结界开门事件
    EventMgr.register("UIDungeonMain", event.CRYSTAL_ACTIVE_DOOR, function(args)
        local function callback()
            self.grids[self.doorIndex]:openEntry();
        end

        -- 延迟下播放光效
        performWithDelay(self, callback, 0.6);
    end);

    -- 关注关注召唤兽死亡后离开迷宫事件
    EventMgr.register("UIDungeonMain", event.DIE_LEAVE_DUNGEON, function(args)

        local function showReviveForm()
            -- 显示复活界面
            if UIMgr.getCurrentScene():isOpen("UIDungeonRevive") then
                UIMgr.getCurrentScene():removeFormByName("UIDungeonRevive");
            end

            local uiDungeonRevive = UIDungeonRevive.create();
            UIMgr.getCurrentScene():addForm(uiDungeonRevive);
        end

        local function callback()
            hideTargetSelectMask();
            -- 指挥官冈布奥ID
            local commanderPetId = 59;
            local msgList = {getLocStr("dreamland_instance_fail")};
            local heroSpeak = UIHeroSpeak.create(self, commanderPetId, msgList, "B_FLY_IN", "B_FLY_OUT", nil, showReviveForm);
            AudioM.playFx("slime_swordman_app");
            UIMgr.getCurrentScene():addForm(heroSpeak);
        end

        showSummonDieEffect(args.pos, callback);
    end);

    -- 怪物转换成建筑
    EventMgr.register("UIDungeonMain", event.SUMMON_TRANS_BUILDING, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];
        local needEffect = args.needEffect;

        grid:onSummonTransBuilding();

        -- 如果需要播放光效
        if needEffect then
            playEffect(grid, 1261, -18, 140, nil, 1.3);
            playEffect(grid, 1262, -20, 120, nil, 1.3);
        end

        -- 指挥官的特殊处理
        if args.is_commander == true then
            -- 指挥官冈布奥ID
            local function getCommanderSlime()
                DungeonActionM.go("do_commander_pet_bonus", pos);
            end

            -- 奖励指挥官
            local commanderPetId = 59;
            local heroSpeak;
            local msgList;
            if args.need_bonus then
                msgList = {getLocStr("commander_comming")};
                heroSpeak = UIHeroSpeak.create(self, commanderPetId, msgList, "B_FLY_IN", "B_FLY_OUT", nil, getCommanderSlime);
            else
                msgList = {getLocStr("dreamland_instance_suc")};
                heroSpeak = UIHeroSpeak.create(self, commanderPetId, msgList, "B_FLY_IN", "B_FLY_OUT");
            end
            AudioM.playFx("slime_swordman_app");
            UIMgr.getCurrentScene():addForm(heroSpeak);
        end
    end);

    -- 使用追击炮弹
    EventMgr.register("UIDungeonMain", event.SLIME_STAR_DAMAGE, function(args)
        local posList = args["pos_list"];
        playMortarShellsEffect(posList);
    end);

    -- 发动银之手
    EventMgr.register("UIDungeonMain", event.ATTACK_STEALING, function(args)
        local pos = args["pos"];

        -- 获取目标节点
        local uiLevel = UIDungeonMgr.getCurLevel();
        local uiGrid  = uiLevel:getAllGrids()[pos];

        -- 播放特效
        local function playEffect()
            attackStealingEffect(uiGrid.node);
            return 0.4;
        end

        uiGrid.actionManager:addAction(uiGrid.gridData, playEffect, "castSkill", { }, GRID_ACTION_PHASE_THROW_FLY);
    end);

    -- 发动货币战争
    EventMgr.register("UIDungeonMain", event.SEARCH_POINT_DAMAGE, function(args)
        local posList = args["pos_list"];
        searchPointDamageEffect(posList, node);
    end);

    -- 发动大闹天宫
    EventMgr.register("UIDungeonMain", event.MONKEY_GLOBAL_OPEN_EFFECT, function(args)
        -- 先放一个空的行为进管理器，这么做是为了让完美击杀不马上显示出来
        local function waitTime()
            return 0;
        end
        self.grids[1].actionManager:addAction(self.grids[1].gridData, waitTime, "castSkill", { }, GRID_ACTION_PHASE_GRID_OPENED);

        naughtyMonkeyEffect();
    end);

    -- 发动神圣重生
    EventMgr.register("UIDungeonMain", event.HOLY_REVIVE_EFFECT, function(args)
        holyReviveEffect(self.node);
    end);

    -- 黑武士冈布奥对话
    EventMgr.register("UIDungeonMain", event.DARK_KNIGHT_DIALOG, function(args)
        local pos = args.pos;
        local templateId = FormulaM.invoke("CALC_DARK_KNIGHT_TEMPLATEID", ME.user);

        local function callback()
            DungeonActionM.go("do_building_bonus", pos);
        end

        require "game/ui/form/main/UIDialogue";
        local uiDialog = UIDialogue.create(templateId, callback);
        UIMgr.getCurrentScene():addForm(uiDialog);

    end);

    -- 建筑奖励事件
    EventMgr.register("UIDungeonMain", event.GRID_SHOW_BUILDING_BONUS, function(args)
        local pos   = args.pos;
        local bonus = args.bonus;
        local tips  = args.tips;
        local grid = self.grids[pos];
        local class = grid.gridData.class;

        -- 如果这个建筑有光环属性，需要刷新下所有怪物的攻击血量
        local grid = self.grids[pos];
        if grid.gridData.assist_prop then
            for _, g in pairs(self.grids) do
                g:updateHPAndAttack();
            end

            -- 刷新状态条
            self.dungeonStatusBar:updateBar();
        end

        grid:onShowBuildingBonus(bonus, class);

    end);

    -- 阿努比斯回答
    EventMgr.register("UIDungeonMain", event.ANSWER_ANUBIS_RET, function(args)
        local isOk = args.isOk;
        local bonus = args.bonus;
        local damage = args.damage;
        local pos = args.pos;

        local grid = self.grids[pos];

        -- 回答正确播放建筑奖励
        if isOk then
            alert(getLocStr("answer_anubis_ok"));
            grid:onShowBuildingBonus(bonus);
        else
            alert(getLocStr("answer_anubis_fault"));

            playBuildingDisappear(grid);

            -- 播放伤害效果
            self:playHeroDeadlyDamaged(damage);
        end
    end);

    EventMgr.register("UIDungeonMain", event.USE_ALLOPATRIC_WICK, function(args)
        local pos = args["pos"];
        local grids = UIDungeonMgr.getCurLevel():getAllGrids();
        playEffect(grids[pos].node, 54);
    end);

    -- 卷轴触发时间停止
    EventMgr.register("UIDungeonMain", event.SPELL_STOP_TIME, function()
        -- 解决SLIMEC-4206和4247问题
        -- if not ArenaM.isInArena() then
        --     return;
        -- end

        playTimeCeaseEffect();
    end);

    -- 打开保险箱失败
    EventMgr.register("UIDungeonMain", event.OPEN_STRONGBOX_FAIL, function(args)
        local pos = args["pos"];
        local grid = self.grids[pos];

        self:lockScreen(2);

        -- 振动
        local function doShakeEffect()
            playShakeEffect(grid, 0.03, 4, 4);
            return 0.5;
        end
        grid.actionManager:addAction(grid.gridData, doShakeEffect, "doShakeEffect", {}, GRID_ACTION_PHASE_THROW_FLY);

        -- 先消失
        local function doDisappear()
            playBuildingDisappear(grid);
            return 0.9;
        end
        grid.actionManager:addAction(grid.gridData, doDisappear, "doDisappear", {}, GRID_ACTION_PHASE_MONSTER_DAMAGED);

        -- 爆炸
        local function doBomEffect()
            -- 爆炸光效
            playEffect(grid, 1233, 0, 20);
            return 0.5;
        end
        grid.actionManager:addAction(grid.gridData, doBomEffect, "doBomEffect", {}, GRID_ACTION_PHASE_MONSTER_UPDATE);

        -- 最后回答问题
        local function answerFail()
            DungeonActionM.go("open_sky_strongbox", pos, 1);
            return 0.1;
        end

        grid.actionManager:addAction(grid.gridData, answerFail, "answerFail", {}, GRID_ACTION_PHASE_MONSTER_DIED);

        EventMgr.fire(event.PLAYER_MOVE);
    end);

    -- 刷怪建筑失效
    EventMgr.register("UIDungeonMain", event.MONSTER_BUILDING_DISABLE, function(args)
        local pos = args["pos"];
        local grid = self.grids[pos];
        -- 爆炸
        local function doBomEffect()
            -- 爆炸光效
            playBuildingDisappearEffect(grid, "bomb");
            return 0.5;
        end
        grid.actionManager:addAction(grid.gridData, doBomEffect, "doBomEffect", {}, GRID_ACTION_PHASE_MONSTER_DAMAGED);

        -- 消失
        local function doDisappear()
            playBuildingDisappear(grid);

            -- 隐藏cd
            local cdNode = findChildByName(grid.node, "cd_node");
            cdNode:setVisible(false);
            return 0.9;
        end
        grid.actionManager:addAction(grid.gridData, doDisappear, "doDisappear", {}, GRID_ACTION_PHASE_MONSTER_UPDATE);
    end);

    -- 作战平台关闭
    EventMgr.register("UIDungeonMain", event.CLOSE_MACHINE_PLATFORM, function(args)
        local pos = args["pos"];
        local grid = self.grids[pos];
        -- 播放建筑消失
        playBuildingDisappear(grid);
    end);

    -- 注册史莱姆怪物死亡回调
    EventMgr.register("UIDungeonMain", event.SLIME_MONSTER_DIE, function(param)
        if ActivityDungeonM.isInActivityBossLayer() then
            -- 活动副本，boss死亡门直接显示
            local doorGrid = self.grids[self.doorIndex];
            if not doorGrid.canEnterNextLevel then
                doorGrid:openEntry(true);
            end
        end
    end);

    -- 注册观众怪物死亡回调
    EventMgr.register("UIDungeonMain", event.AUDIENCE_MONSTER_DIE, function(param)
        local monster = param.monster;
        if monster and ActivityDungeonM.isInActivityBossLayer() then
            local classId = monster:getClassId();
            local pos = monster:getPos();
            local dbase = NormalAudienceM.queryAudience(classId, "dbase");
            local grid = self.grids[pos];

            -- 活动副本，观众死亡光效
            local function dieEffect()
                if dbase and dbase["died_effect"] then
                    if dbase["died_effect"] == "broken_dish" then
                        -- 随机几个位置做爆炸
                        return playBrokenDishEffect(self:getEffectParent());
                    elseif dbase["died_effect"] == "bomb" then
                        -- 炸弹
                        playEffect(grid, 55, 0, 80);
                        return 1.0;
                    end
                end
                return 0;
            end

            grid.actionManager:addAction(grid.gridData, dieEffect, "dieEffect", {}, GRID_ACTION_PHASE_MONSTER_DAMAGED);
        end
    end);

    -- 注册爱丽丝古堡的受伤事件
    EventMgr.register("UIDungeonMain", event.ALICE_INSTANCE_DAMAGE, function(param)
        local pos = param.pos;
        local grid = self.grids[pos];
        local gridType = param.type;


        if gridType == GRID_TYPE_DESK_CLOCK then
            local targetNode = findChildByName(self.node, "CT2/model_node");
            local gridData = grid.gridData;
            local class = gridData.class;
            local failTip = BuildingBonusM.queryBuildingView(class, "fail_tip");
            alert(failTip);

            -- 短暂锁屏
            require "game/ui/form/common/UIBlackForm"
            local uiForm = UIBlackForm.create(3, nil, nil, grid);
            UIMgr.getCurrentScene():addForm(uiForm);

            uiForm:playDeskClockEffect();
        elseif gridType == GRID_TYPE_BLOODY_TESTBED then

            alert(getLocStr("material_boom"));

            -- 短暂锁屏
            require "game/ui/form/common/UIBlackForm"
            local uiForm = UIBlackForm.create(3, nil, nil, grid);
            UIMgr.getCurrentScene():addForm(uiForm);

            uiForm:playBoodyTestbedFailEffect();
        elseif gridType == GRID_TYPE_SEAL_ROOM then
            alert(getLocStr("dark_damage"));

            -- 短暂锁屏
            require "game/ui/form/common/UIBlackForm"
            local uiForm = UIBlackForm.create(3, nil, nil, grid);
            UIMgr.getCurrentScene():addForm(uiForm);

            uiForm:playBreakSealFailEffect();

        elseif gridType == GRID_TYPE_GUARD_STATUE then
            if param.isOk == true then
                local gridData = grid.gridData;
                local class = gridData.class;
                local bonusTip = BuildingBonusM.queryBuildingView(class, "bonus_tip");
                alert(bonusTip);
                playBuildingShadow(grid);
            end
        end
    end);

    -- 棋桌失效
    EventMgr.register("UIDungeonMain", event.CHESS_TABLE_DISABLE, function(args)
        local pos = args["pos"];
        local grid = self.grids[pos];
        -- 播放建筑消失
        playBuildingDisappear(grid);
    end);

    -- 特殊入场检查
    EventMgr.register("UIDungeonMain", event.SPECIAL_ENTER_EFFECT, function(args)
        self:specialEnterEffect();
    end);

    -- 属性增加
    EventMgr.register("UIDungeonMain", event.ATTRIB_ADD, function(args)
        self:attribAdd(args);
    end);

    -- 变异体冈布奥失效
    EventMgr.register("UIDungeonMain", event.WAKUSEI_EXCHANGE, function(args)
        local pos = args["pos"];
        local bonus = args["bonus"];
        local grid = self.grids[pos];
        local waikuseiId = grid.gridData.wakusei_id;

        -- 播放建筑消失/奖励
        if type(bonus) == "table" and #bonus >= 0 then
            grid:playBonusListEffect(bonus, true);
        else
            playBuildingDisappear(grid);
        end

        local tip = WakuseiM.query(waikuseiId, "bonus_tip");
        alert(tip);

        -- 更新技能
        local skillEnergyBarNode = findChildByName(self.node, "CT1/skill_energy_bar");
        skillEnergyBarNode:removeAllChildren();
        self:initSkillEnergyBar();
    end);

    -- 刷新石柱的显示
    EventMgr.register("UIDungeonMain", event.UPDATE_STONE_STATUS, function(args)
        local pos = args.pos;
        local status = args.status;

        local grid = self.grids[pos];
        if status == STONE_STATE_OPENED then
            -- 石柱开启
            grid:onOpenBossStone(grid);
        elseif status == STONE_STATE_CLOSED then
            -- 石柱关闭
            grid:onCloseBossStone(grid);
        else
            -- 石柱消失
            grid:onStoneDisappear(grid);
        end

        -- 属性可能提升了，刷新一下界面
        self:updateUI();

        -- 刷新状态条
        self.dungeonStatusBar:updateBar();
    end);

    -- 刷新CD显示
    EventMgr.register("UIDungeonMain", event.UPDATE_STONE_CD, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];

        grid:updateCountDown();
    end);

    -- 反叛军奖励
    EventMgr.register("UIDungeonMain", event.REBELS_BONUS, function(args)
        local pos = args.pos;
        local bonus = args.bonus;
        local grid = self.grids[pos];
        grid:playBonusListEffect(bonus, args.disappear);
    end);

    -- 学习升级技能
    EventMgr.register("UIDungeonMain", event.BISHOP_SKILL_UPGRADE, function(args)
        local bishop = ME.user.bishop;
        local pos = bishop:getPos();
        local grid = self.grids[pos];

        -- 刷新技能气泡
        grid:redrawBishopSkill();

        -- 刷新攻击和血量显示
        grid:redrawBishopAttackAndHp();
    end);

    -- 治疗
    EventMgr.register("UIDungeonMain", event.CURE_BISHOP, function(args)
        local bishop = ME.user.bishop;
        local pos = bishop:getPos();
        local grid = self.grids[pos];
        local addList = args["add_list"];

        -- 刷新技能气泡
        grid:redrawBishopSkill();

        -- 刷新攻击和血量显示
        grid:redrawBishopAttackAndHp();

        -- 面包磨坊格子消失
        playBuildingDisappear(self.grids[args["pos"]]);

        -- 飘血
        grid:bishopCureEffect(addList);
    end);

    -- 复活
    EventMgr.register("UIDungeonMain", event.BISHOP_REVIVE, function(args)
        local bishop = ME.user.bishop;
        local pos = bishop:getPos();
        local grid = self.grids[pos];

        -- 刷新技能气泡
        grid:redrawBishopSkill();

        -- 刷新攻击和血量显示
        grid:redrawBishopAttackAndHp();

        -- 播放光效
        playBuildingStrongLight(grid);
    end);

    -- 主动攻击
    EventMgr.register("UIDungeonMain", event.BISHOP_USE_SKILL, function(args)
        local bishop = ME.user.bishop;
        local pos = bishop:getPos();
        local grid = self.grids[pos];
        local targetPos = args["pos"];
        local targetGrid = self.grids[targetPos];

        local function doBishopAttack()
            -- 刷新技能气泡
            grid:redrawBishopSkill();

            -- 刷新攻击和血量显示
            grid:redrawBishopAttackAndHp();

            -- 播放攻击动作
            grid:switchBishopAction(SUMMON_ACTION_ATTACK);

            -- 天堂之拳光效
            playEffect(targetGrid, 44);
            return 0.5;
        end

        grid.actionManager:addAction(grid.gridData, doBishopAttack, "doBishopAttack", {}, GRID_ACTION_PHASE_THROW_PREPARE);
    end);

    -- 主动攻击
    EventMgr.register("UIDungeonMain", event.BISHOP_ATTACK, function(args)
        local bishop = ME.user.bishop;
        local pos = bishop:getPos();
        local grid = self.grids[pos];

        local function doBishopAttack()
            -- 刷新技能气泡
            grid:redrawBishopSkill();

            -- 刷新攻击和血量显示
            grid:redrawBishopAttackAndHp();

            -- 播放攻击动作
            grid:switchBishopAction(SUMMON_ACTION_ATTACK);

            return 0.5;
        end

        grid.actionManager:addAction(grid.gridData, doBishopAttack, "doBishopAttack", {}, GRID_ACTION_PHASE_MONSTER_DAMAGED);
    end);

    -- 主教死亡
    EventMgr.register("UIDungeonMain", event.BISHOP_DIE, function(args)
        local bishop = ME.user.bishop;
        local pos = bishop:getPos();
        local grid = self.grids[pos];

        -- 刷新技能气泡
        grid:redrawBishopSkill();

        -- 刷新攻击和血量显示
        grid:redrawBishopAttackAndHp();
    end);

    -- 迷宫奖励信息
    EventMgr.register("UIDungeonMain", event.CG_UNREAL_BONUS, function(args)
        local pos = args.pos;
        local bonus = args.bonus;
        local grid = self.grids[pos];

        -- 根据角色ID，获得奖励提示
        local roleType = grid.gridData.role_type;

        local tip = CreationGateM.queryDialogInfo(roleType, "bonus_tip");

        -- 如果奖励的是peter建筑需要消失
        local isDisappear = false;
        if roleType == 5 and args.bonus[1][1] == 3 then
            tip = getLocStr("peter_join_us");

            isDisappear = true;
        end

        alert(tip);

        -- 有些奖励需要分几次飞入,
        local bonusList = FormulaM.invoke("DIVISION_CG_BONUS", bonus);

        grid:playBonusListEffect(bonusList, isDisappear);
    end);

    -- 注册雕像激活事件
    EventMgr.register("UIDungeonMain", event.ACTIVE_EVIL_STATUE, function(args)
        local pos   = args.pos;
        local bonus = args.bonus;
        local grid  = self.grids[pos];

        -- 播放光效
        playBuildingStrongLight(grid);

        -- 奖励飞入
        grid:playBonusListEffect(bonus, false);
    end);

    -- 注册雕像献祭事件
    EventMgr.register("UIDungeonMain", event.EVIL_STATUE_SUBMIT, function(args)
        local pos        = args.pos;
        local bonus      = args.bonus;
        local isUpgrade  = args.isUpgrade;
        local remainGrid = true;

        if isUpgrade then
            playEvilSumitBonusEffect(self.grids[pos], args.classify, args.upgradeLevel, args.bonusList);
        end
    end);

    -- 魔镜
    EventMgr.register("UIDungeonMain", event.MAGIC_MIRROR_TRIGGER, function(args)
        local type = args["type"];
        local mirrorPos = args["pos"];
        if type ~= "summon" then
            return;
        end

        -- 吸收效果
        local targetNode = self.grids[mirrorPos];
        playMirrorEffect(targetNode);
    end);

    -- 观众换图
    EventMgr.register("UIDungeonMain", event.AUDIENCE_CHANGE_ICON, function(args)
        local pos = args["pos"];
        local iconName = args["icon"];
        local grid = self.grids[pos];
        local icon = grid.audienceImg;

        if not icon or icon.changeIcon == iconName then
            return;
        end

        -- 换图
        icon:loadTexture(getLevelItemIconPath(iconName));
        icon.changeIcon = iconName;
    end);

    -- 观众死亡奖励
    EventMgr.register("UIDungeonMain", event.AUDIENCE_DIE_BONUS, function(args)
        local pos = args["pos"];
        local bonus = args["bonus"];
        local grid = self.grids[pos];

        local function dieBonus()
            grid:playBonusListEffect(bonus);
            return 0.5;
        end

        grid.actionManager:addAction(grid.gridData, dieBonus, "dieBonus", {}, GRID_ACTION_PHASE_MONSTER_DIED);
    end);

    -- 工程学手册赋予装备属性
    EventMgr.register("UIDungeonMain", event.MANUAL_APPLY_EQUIP_PROP, function()
        self:updateHPAndAttack();
        self:updateUI();
    end);

    -- 蓝鲨奖励事件
    EventMgr.register("UIDungeonMain", event.BS_POLICE_BONUS, function(args)
        local pos   = args.pos;
        local bonus = args.bonus;
        local grid  = self.grids[pos];

        -- 播放建筑消失
        playBuildingDisappear(grid);

        -- 奖励飞入
        grid:playBonusListEffect(bonus, false);
    end);

    -- 学习工程学手册成功事件
    EventMgr.register("UIDungeonMain", event.LEARN_MANUAL_SUCCEED, function(args)
        self:updateUI();
        self:checkHpAlert();

        -- 弹出提示
        if args.is_alert then
            local id = args["id"];
            local name = EngineeringManualM.query(id, "name");
            local bonusId = EngineeringManualM.query(id, "bonus_id");

            local alertText;
            local info;
            if id == 1   or id == 201 or id == 205 or id == 209 or
               id == 213 or id == 301 or id == 302 or id == 401 or
               id == 405 or id == 409 or id == 413 or id == 501 then
                info = EngineeringManualM.queryBonus(bonusId, "lv1")["bonus"];
                info2 = EngineeringManualM.queryBonus(bonusId, "lv1")["summon"];
                if info then
                    alertText = string.format(getLocStr("engineering_manual_study_success_make"), ItemM.query(info[2], "name"));
                elseif info2 then
                    alertText = string.format(getLocStr("engineering_manual_study_success_make"), SummonM.query(info2[1], "name"));
                end
            elseif id == 101 or id == 108 then
                alertText = getLocStr("engineering_manual_study_success_inject");

                info = EngineeringManualM.queryBonus(bonusId, "lv1")["manual_cure"];
                local value = FormulaM.invoke("CALC_MANUAL_CURE", info);
                local attribName = FieldsM.getFieldName(info["attrib"]);

                alertText = string.format(alertText, name, attribName, value);

                local arr = string.explode(alertText, "\n");
                splitAlert(arr);
                return;
            elseif id == 507 then
                alertText = getLocStr("engineering_manual_study_success_recover");
            else
                alertText = getLocStr("engineering_manual_study_success");
            end

            alert(string.format(alertText, name));
        end
    end);

    -- 充能平台触发
    EventMgr.register("UIDungeonMain", event.ENERGY_PLATFORM_TRIGGER, function(args)
        local pos   = args.pos;
        local target = args.target;
        local grid  = self.grids[pos];

        -- 播放建筑扩散光效
        -- playBuildingStrongLight(grid, false);
        playEffect(grid, 1208, 0, -20);
    end);

    -- 向阿瓦隆实验室提交物品事件
    EventMgr.register("UIDungeonMain", event.SUBMIT_TO_AVALON_LAB, function(args)
        local pos = args["pos"];

        -- 成功了，刷新下UI
        local isSucc = args["isSuccess"];
        if isSucc then
            self:updateUI();
            self:checkHpAlert();
        end

        -- 刷新下格子的表现
        uiGrid = self.grids[pos];
        uiGrid:submitToAvalonLab(args);
    end);

    -- 地牢天空战结果事件
    EventMgr.register("UIDungeonMain", event.DUNGEON_SKY_COMBAT_RESULT, function(para)
        self.dungeonActionManager:pause();

        -- 干掉锁屏容器的黑色蒙版
        local panel = findChildByName(self.node, "lock_panel");
        if panel.blackMask then
            panel.blackMask:removeFromParent();
            panel.blackMask = nil;
        end
        panel:setVisible(false);

        -- 关闭预警界面
        AlertStateM.clearAllStates();

        require "game/ui/form/sky_city/UISkyCityCombat"

        local UISkyCityCombat = UISkyCityCombat.create(para.battle, true);
        UIMgr.getCurrentScene():addForm(UISkyCityCombat);

        -- 关闭通讯中界面
        closeCommunicatingForm();

        -- 添加一个带纯黑蒙版的UIBlackMask界面
        require "game/ui/form/common/UIBlackMask"
        local form = UIBlackMask.create();
        UIMgr.getCurrentScene():addForm(form);

        local blackMask = findChildByName(form.node, "black_mask");
        local fadeOut = cc.FadeOut:create(1);
        local callFunc = cc.CallFunc:create(function ()
            blackMask:setOpacity(255);

            UIMgr.getCurrentScene():removeFormByName("UIBlackMask");
        end)
        blackMask:runAction(cc.Sequence:create(fadeOut, callFunc));

        -- 记录地牢天空战扣除的怪物血量
        self.skyCombatCostHp = para.costHp;
    end);

    -- 地牢天空战开始事件
    EventMgr.register("UIDungeonMain", event.DUNGEON_SKY_COMBAT_START, function(para)
        self.dungeonActionManager:pause();

        -- 显示通讯中界面
        showCommunicatingForm();

        -- 锁屏容器添加黑色蒙版，并且淡入
        local panel = findChildByName(self.node, "lock_panel");
        local width = panel:getContentSize().width;
        local height = panel:getContentSize().height;
        local icon = ccui.ImageView:create();
        icon:loadTexture(getCommonIconPath("mengban_black"));
        icon:setPosition(width / 2, height / 2);
        icon:setScale9Enabled(true);
        icon:setCapInsets(cc.rect(0, 0, 0, 0));
        icon:setContentSize(cc.size(width * 2, height * 2));

        panel:addChild(icon);
        panel.blackMask = icon;
        panel:setVisible(true);

        icon:setOpacity(0);
        local fadeIn = cc.FadeIn:create(1);
        icon:runAction(cc.Sequence:create(fadeIn));
    end);

    -- 摧毁毒气装置事件
    EventMgr.register("UIDungeonMain", event.DESTORY_GAS_DEVICE, function(args)
        local pos = args["pos"];

        -- 刷新一下界面
        self:updateUI();

        -- 刷新状态条
        self.dungeonStatusBar:updateBar();

        -- 移除警戒
        AlertStateM.clearState(AlertStateM.ALERT_STATE_GAS_DEVICE);

        -- 刷新下界面显示
        local uiGrid = self.grids[pos];
        uiGrid:destoryGasDevice(args);
    end);

    -- 摧毁钢铁之门事件
    EventMgr.register("UIDungeonMain", event.DESTORY_STEEL_DOOR, function(args)
        local pos = args["pos"];

        -- 刷新下界面显示
        local uiGrid = self.grids[pos];
        uiGrid:destorySteelDoor(args);

        -- 播放音效
        AudioM.playFx("destory_steel_door");
    end);

    -- 建筑转换
    EventMgr.register("UIDungeonMain", event.BUILDING_TRANS_BUILDING, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];
        local effectId = args["effectId"];

        -- 原建筑是钢铁之门，则播放摧毁音效
        if args.class == 3006 then
            AudioM.playFx("destory_steel_door");
        end

        -- 转换建筑
        grid:onBuildingTransBuilding(effectId);

        -- 格子表现
        grid:steelDoorTrans(args);
    end);

    -- 刷新定时炸弹剩余时间
    EventMgr.register("UIDungeonMain", event.UPDATE_TIME_BOMB_TIME, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];

        -- 更新剩余时间
        grid:updateTimeBombTime();

        -- 界面提示
        alert(getLocStr("avalon_time_bomb_hint"));
    end);

    -- 领取智能电脑奖励
    EventMgr.register("UIDungeonMain", event.TAKE_PC_BONUS, function(args)
        local bonusId = args.bonusId;
        local bonus = args.bonus;
        local pos = args.pos;
        local grid = self.grids[pos];

        local name = IntelligencePCM.queryBonus(bonusId, "title");
        local msg = string.format(getLocStr("pc_bonus_tip"), name);

        local list = splitString(msg, "\n");
        splitAlert(list, 0.8);

        local function delay()
            -- 奖励飞入
            grid:playBonusListEffect(bonus, false);
        end
        performWithDelay(self, delay, 0.5);
    end);

    -- 领取破旧奖励
    EventMgr.register("UIDungeonMain", event.TAKE_BROKEN_PC_BONUS, function(args)
        local bonus = args.bonus;
        local pos = args.pos;
        local grid = self.grids[pos];

        local class = grid.gridData.class;
        local buildingInfo = BuildingBonusM.queryBuildingView(class);
        local msg = buildingInfo["bonus_tip"];

        local list = splitString(msg, "\n");
        splitAlert(list);

        -- 爆炸效果
        playBuildingDisappearEffect(grid, "bomb");

        -- 奖励飞入/建筑消失
        grid:playBonusListEffect(bonus, true);

        EventMgr.fire(event.PLAYER_MOVE, { });
    end);

    -- 阿瓦隆巨炮攻击
    EventMgr.register("UIDungeonMain", event.AVALON_CANNON_ATTACK, function(args)
        local pos = args["pos"];
        local grid = DungeonM.getGridByPos(pos);

        if grid and grid.avalon_cannon then
            -- 阿瓦隆巨炮攻击特效
            avalonCannonAttackEffect(grid);
        end

        -- 播放音效
        AudioM.playFx("avalon_cannon_atack");
    end);

    -- 召唤兽使用主动技
    EventMgr.register("UIDungeonMain", event.SUMMON_CAST_SKILL, function(args)
        local pos = args["pos"];
        local grid = self.grids[pos];
        local targetPos = args["targetPos"];
        local targetGrid = self.grids[targetPos];
        local targets = args["targets"];

        -- 重绘技能图标
        grid:redrawSummonSkill();

        -- 重绘倒计时
        grid:updateCountDown();

        local function doSummonAttack()
            -- 主动技动作
            grid:switchSummonAction(SUMMON_ACTION_CAST_SKILL);
            return 0.5;
        end

        grid.actionManager:addAction(grid.gridData, doSummonAttack, "doSummonAttack", {}, GRID_ACTION_PHASE_MONSTER_DAMAGED);
        if type(targets) == "table" then
            for _, monster in ipairs(targets) do
                local monsterPos = monster:getPos();
                local monsterGrid = self.grids[monsterPos];
                monsterGrid.actionManager:addAction(monsterGrid.gridData, function() return 0.5; end, "summonCastSkill", {}, GRID_ACTION_PHASE_THROW_PREPARE);
            end
        else
            targetGrid.actionManager:addAction(targetGrid.gridData, function() return 0.5; end, "summonCastSkill", {}, GRID_ACTION_PHASE_THROW_PREPARE);
        end
    end);

    -- 工程学手册刷新召唤兽属性
    EventMgr.register("UIDungeonMain", event.MANUAL_SUMMON_PROP, function(args)
        local pos = args.pos;
        local grid = self.grids[pos];

        -- 重绘技能图标
        grid:redrawSummonSkill();
        grid:updateSummonHPAndAttack();
    end);

    -- 阿瓦隆巨炮装填弹药
    EventMgr.register("UIDungeonMain", event.AVALON_CANNON_LOAD_AMMUNITION, function(args)
        local effectParent = UIDungeonMgr:getCurLevel():getEffectParent();
        local node = effectParent:getChildByName("avalonCannon");

        local _, effect = playEffect(node, 1336, 5, 65);
        effect:setName("cannon_effect");
    end);

    -- 通用赌桌赌桌事件
    EventMgr.register("UIDungeonMain", event.NORMAL_GAMBLE, function(args)
        local pos    = args.pos;
        local bonus  = args.bonus;
        local grid   = self.grids[pos];
        local result = args.result;
        local optionId = args.optionId;
        local optionInfo = DungeonGambleM.queryOption(optionId);

        -- 赌博结果
        if result == 1 then
            if #bonus > 0 then
                grid:playBonusListEffect(bonus, false);
            end
            alert(optionInfo["win_tip"]);
        else
            alert(optionInfo["loss_tip"]);
        end
    end);

    -- 蒸汽机械攻击
    EventMgr.register("UIDungeonMain", event.STEAM_ATTACK, function(args)
        local pos = args.pos;
        local damage = args.damage;
        local grid = self.grids[pos];

        -- 机械攻击光效
        if grid.item.animation then
            grid.item.animation:play(1);
        end

        -- 播放音效
        AudioM.playFx("steam_tank_attack");

        -- 玩家受重创特效
        performWithDelay(self.node, function() self:playHeroDeadlyDamaged(damage, 1337, {0, -15}); end, 0.3);
    end);

    -- 偷窃黑市摊贩
    EventMgr.register("UIDungeonMain", event.STEAL_BLACK_MARKET, function(args)
        local pos = args.pos;
        local result = args.result;
        local bonus = args.bonus;
        local grid = self.grids[pos];

        local class = grid.gridData.class;
        local buildingInfo = BuildingBonusM.queryBuildingView(class);

        if result == "success" then
            -- 成功
            grid:playBonusListEffect(bonus, true);
            tip = buildingInfo["bonus_tip"];
        else
            -- 失败
            playBuildingDisappear(grid);
            tip = buildingInfo["fail_tip"];
        end

        local list = splitString(tip, "\n");
        splitAlert(list);
    end);

    -- 蓝鲨狂暴
    EventMgr.register("UIDungeonMain", event.TO_MONSTER_SKILL, function(args)
        local pos = args["pos"];
        local classId = args["baseClassId"];
        local skillId = args["skillId"];
        local grid = self.grids[pos];
        if classId == 4030 then
            -- 蓝鲨狂暴
            local function delay()
                alert(getLocStr("bs_rage_tip"));
                -- 虚影
                playSlimeVirtualShadowEffect(120, pos);
            end
            performWithDelay(self.node, delay, 1);
        end
    end);

    -- 关注更新警戒事件
    EventMgr.register("UIDungeonMain", event.UPDATE_ALERT, function(args)
        local classId = args["classId"];

        -- 检查冰霜泰坦警戒
        if classId == 510 then
            self:checkIceTitantAlert();
        end
    end);

    -- 召唤兽工坊升级
    EventMgr.register("UIDungeonMain", event.SUMMON_BLACKSMITH_UPGRADE, function(args)
        local id = args.id;

        local summonId;
        local gridList = {};
        for _, summonId in ipairs(SummonBlacksmithM.queryBonus(id, "summon") or {}) do
            gridList = table.append(gridList, SummonM.findSummonsByClassId(summonId));
        end

        if #gridList <= 0 then
            -- 没有召唤兽格子
            return;
        end

        for _, g in pairs(gridList) do
            local pos = g:getPos();
            grid = self.grids[pos];
            grid:updateSummonHPAndAttack();
        end
    end);

    -- 创世之门夹层内的创世之门奖励
    EventMgr.register("UIDungeonMain", event.CREATION_DOOR_BONUS, function(args)
        local pos = args.pos;
        local bonus = args.bonus;
        local grid = self.grids[pos];

        -- 先提示
        alert(getLocStr("creation_door_tip2"));

        grid:playBonusListEffect(bonus, false);

        self:playHPAlertEffect();

        local function delayCallback()
            self:stopHPAlertEffect();
        end

        performWithDelay(self, delayCallback, 4);

        -- 屏幕震动
        playShakeEffect(UIDungeonMgr.getCurLevel(), 0.03, 4, 4);
    end);

    -- 关注唤醒冈布奥事件
    EventMgr.register("UIDungeonMain", event.WAKE_UP_SLIME, function(args)
        local summonId = args.summon_id;
        -- XXX加入了你的队伍！
        alert(string.format(getLocStr("confuse_slime_join"), SummonM.query(summonId, "name")))
    end);

    -- 关注格子变化为怪物事件
    EventMgr.register("UIDungeonMain", event.GRID_TRANS_TO_MONSTER, function(args)
        local source    = args.source;
        local skillId   = args.skill_id;
        local dbase     = SkillM.query(skillId, "dbase");

        if dbase["talk"] then
            local grid = self.grids[source:getPos()];
            grid:doRoleTalk(dbase["talk"]);
        end

    end);
end

-- 播放玩家受重创特效
function UIDungeonMain:playHeroDeadlyDamaged(damage, effectId, offset)
    -- 屏幕震动
    playShakeEffect(UIDungeonMgr.getCurLevel(), 0.03, 4, 4);
    local effectParent = UIDungeonMgr:getCurLevel():getEffectParent();
    local heroPos = UIDungeonMgr.getCurLevel():getHeroPos();

    -- 内爆术
    local effectId = effectId or 55;
    offset = offset or {10, -65};
    playEffect(effectParent, effectId, heroPos.x + offset[1], heroPos.y + offset[2]);

    -- 播放伤害效果
    EventMgr.fire(event.HERO_DAMAGED, { ["damage"] = damage, });

    -- 播放玩家受创动作
    UIDungeonMgr:getCurLevel():switchHeroAction(HERO_ACTION_DAMAGED);
end

-- 注册back按钮的处理函数
function UIDungeonMain:initBackEventHandler()
    exitGamePrompt(self);
end

-- 处理怪物洞穴
function UIDungeonMain:handleCave()
    local isShowMask = false;
    local isHideDoor = false;
    local caveMaskNode = self:getCaveMaskNode();
    local caveMaskNode2 = self:getCaveMaskNode2();
    local lightNode = findChildByName(self.node, "CT1/cave_mask/mask_light_node");
    local caveMaskParent = findChildByName(self.node, "CT1/cave_mask");
    local exitNode = findChildByName(self.node, "CT1/cave_mask/exit_node");
    local exitImg = findChildByName(exitNode, "exit");

    if UnrealDungeonM.isInUnreal() then
        -- 夹层中
        local unrealId = UnrealDungeonM.getUnrealId();
        local dbase = UnrealDungeonM.query(unrealId, "dbase");
        if type(dbase) == "table" then
            if dbase["show_cave_mask"] == 1 then
                isShowMask = true;
                local scale = dbase["cave_mask_scale"] or 1.3;
                caveMaskNode:setScale(scale);
            end

            -- 如是沼泽洞窟，并且符合点亮洞窟的条件
            if dbase["swamp_cave"] == 1 and BraveVSDragonM.canLightSwampCave() then
                -- 隐藏遮罩
                isShowMask = false;
                exitNode:setVisible(false);

                -- 播放洞窟点亮的特效
                self:playCaveLightUp(caveMaskNode, lightNode);
            end

            if dbase["door_appear_by_compound"] == 1 or
               dbase["door_appear_by_clear"] == 1 then
                -- 配置了通关或者合成时才出现传送门
                isHideDoor = true;
            end
        end
    elseif DungeonInstanceM.isInInstance() then
        -- 副本中
        local instanceId   = DungeonInstanceM.getCurrentInstanceId();
        local instanceInfo = DungeonInstanceM.getInstanceInfo(instanceId);
        local dbase = instanceInfo["dbase"];
        local maskScale = 1.0;
        local offset = 0;
        isShowMask = true;
        if type(dbase) == "table" then
            if dbase["hide_cave_mask"] == 1 then
                -- 配置了隐藏蒙版
                isShowMask = false;
            end

            if type(dbase["cave_mask_scale"]) == 'number' then
                -- 配置了缩放比例
                maskScale = dbase["cave_mask_scale"];
            end

            if type(dbase["cave_mask_offset"]) == 'number' then
                -- 配置了蒙版偏移
                offset = dbase["cave_mask_offset"];
            end
        end

        caveMaskNode:setScale(maskScale);
        caveMaskNode:setPositionY(caveMaskNode:getPositionY() + offset);

        -- 如果能看到隐藏物件
        if CaveHideItemM.canSeeHideItem() then
            if not caveMaskNode2:isVisible() then
                -- 显示大蒙版（视野变大）
                caveMaskNode2:setVisible(true);
                lightNode:setVisible(true);
                local hideScale = 1.25;
                caveMaskNode2:setScale(hideScale);
                lightNode:setScale(hideScale);
                offset = -40;
                caveMaskNode2:setPositionY(caveMaskNode2:getPositionY() + offset);
                lightNode:setPositionY(lightNode:getPositionY() + offset);

                -- 提示玩家发现隐藏物件
                local tipFlag = DungeonM.getDungeonMixedField("light_cave_tip");

                -- 如果用火把点亮的话只提示一次并且其他情况不提示
                if tipFlag ~= nil then
                    if tipFlag == 1 then
                        alert(getLocStr("light_cave_tip"));
                        DungeonM.setDungeonMixedField("light_cave_tip", 2);
                    end
                else
                    alert(getLocStr("find_hide_item"));
                end

                -- 播放小蒙版消失的特效
                self:playCaveLightUp(caveMaskNode, lightNode);
            end
        else
            caveMaskNode2:setVisible(false);
            lightNode:setVisible(false);
        end
    end

    if not isShowMask then
        return;
    end

    -- 显示遮罩
    caveMaskParent:setVisible(true);

    -- 显示离开的门
    exitImg:loadTexture(getLevelItemIconPath("portal"));
    exitImg:setTouchEnabled(true);
    exitNode:setVisible(not isHideDoor);
    if not isHideDoor then
        portalEffect(exitImg, 60, 66);
    end

    local doorGrid = self.grids[self.doorIndex];
    if doorGrid.onDoorClicked then
        exitImg:addTouchEventListener(doorGrid.onDoorClicked);
    end
end

-- 播放洞窟点亮的特效
function UIDungeonMain:playCaveLightUp(caveMaskNode, lightNode)
    caveMaskNode:setScale(0.8);
    local changeTime = 1.5;
    local waitTime = 0.3;
    local fadeOut = cc.FadeOut:create(changeTime);
    local outDelay = cc.DelayTime:create(waitTime);
    caveMaskNode:runAction(cc.Sequence:create(outDelay, fadeOut));

    -- 播放灯光出现的特效
    for i = 1, 4 do
        local lightBg = findChildByName(lightNode, "mask"..i);
        lightBg:setOpacity(0);
        local fadeIn = cc.FadeIn:create(changeTime);
        local inDelay = cc.DelayTime:create(waitTime);
        lightBg:runAction(cc.Sequence:create(inDelay, fadeIn));
    end
end

-- 获得遮罩节点
function UIDungeonMain:getCaveMaskNode()
    local caveMaskNode = findChildByName(self.node, "CT1/cave_mask/mask_node");
    return caveMaskNode;
end

-- 获得遮罩节点（洞窟隐藏物件专用）
function UIDungeonMain:getCaveMaskNode2()
    local caveMaskNode = findChildByName(self.node, "CT1/cave_mask/mask_node2");
    return caveMaskNode;
end

--如果该部位尚未佩戴道具，则自动装上。
function UIDungeonMain:autoEquip(classId)
    local itemType = ItemM.query(classId, "type");
    if itemType ~= ITEM_TYPE_TREASURE and
        itemType ~= ITEM_TYPE_SPECIAL_TREASURE then
        -- 只有宝物和特殊宝物才能自动装备
        return;
    end

    if ItemM.getAmount(ME.user, classId) <= 0 then
        return;
    end

    local type = EquipM.query(classId, "type");
    if type == EQUIP_TYPE_MEDAL or type == EQUIP_TYPE_WEAPON then
        return ;
    end

    if not EquipM.getEquip(type, 1) then
        DungeonActionM.go("do_equip", 0, classId);
    end
end

-- 更新攻击和血量
function UIDungeonMain:updateHPAndAttack()
    for i = 1, TOTAL_GRID_NUM do
        if not self.grids[i]:isCleared() then

            -- 如果是怪物
            if self.grids[i].gridData:isMonster() then
                self.grids[i]:updateHPAndAttack();
            end
        end
    end
end

-- 显示血量预警效果
function UIDungeonMain:playHPAlertEffect()
    local curScen = UIMgr.getCurrentScene();

    if not curScen.hpAlertEffect then
        UIMgr.getCurrentScene():removeFormByName("UIDungeonHpAlert");
        local alertForm = UIDungeonHpAlert:create();
        curScen:addForm(alertForm);

        curScen.hpAlertEffect = true;
    end
end

-- 停止血量预警效果
function UIDungeonMain:stopHPAlertEffect()
    local curScen = UIMgr.getCurrentScene();

    if curScen.hpAlertEffect then
        local function remove()
            if not curScen.hpAlertEffect then
                curScen:removeFormByName("UIDungeonHpAlert");
            end
        end
        performWithDelay(curScen, remove, 0.01);

        curScen.hpAlertEffect = false;
    end
end

-- 检查血量预警
function UIDungeonMain:checkHpAlert()
    if not DungeonM.isInDungeon() then
        return;
    end

    if self.checkHpAlertNode == nil then
        self.checkHpAlertNode = cc.Node:create();
        self:addChild(self.checkHpAlertNode);
    end

    local function doCheckHpAlert()
        if FormulaM.invoke("NEED_HP_ALERT", ME.user) then
            AlertStateM.setState(AlertStateM.ALERT_STATE_HP);
        else
            AlertStateM.clearState(AlertStateM.ALERT_STATE_HP);
        end
    end
    self.checkHpAlertNode:stopAllActions();
    performWithDelay(self.checkHpAlertNode, doCheckHpAlert, 0.05);
end

-- 检查毒气装置的效果
function UIDungeonMain:checkGasAlert()
    if not DungeonM.isInDungeon() then
        return;
    end

    local grid = AvalonFortM.findGsaDevice();
    if grid and grid.state ~= GRID_STATE_DISABLE then
        AlertStateM.setState(AlertStateM.ALERT_STATE_GAS_DEVICE);
    end
end

-- 显示毒气装置散发毒气效果
function UIDungeonMain:playGasAlertEffect(iconPath)
    local iconPath = iconPath or getCommonIconPath("alert_green");
    local curScen = UIMgr.getCurrentScene();

    if not curScen.gasAlertEffect then
        UIMgr.getCurrentScene():removeFormByName("UICommonAlert");
        local alertForm = UICommonAlert.create(iconPath);
        curScen:addForm(alertForm);

        curScen.gasAlertEffect = true;
    end
end

-- 停止毒气预警效果
function UIDungeonMain:stopGasAlertEffect()
    local curScen = UIMgr.getCurrentScene();

    if curScen.gasAlertEffect then
        local function remove()
            if not curScen.gasAlertEffect then
                curScen:removeFormByName("UICommonAlert");
            end
        end
        performWithDelay(curScen, remove, 0.01);

        curScen.gasAlertEffect = false;
    end
end

-- 显示告警状态
function UIDungeonMain:showAlertState(state)
    -- 先停止所有告警状态
    self:stopAllAlertEffect();

    if state == AlertStateM.ALERT_STATE_HP then
        -- 显示血量告警
        self:playHPAlertEffect();
    elseif state == AlertStateM.ALERT_STATE_LIGHT_STONE then
        self:playStoneAlertEffect(STONE_TYPE_LIGHT);
    elseif state == AlertStateM.ALERT_STATE_DARK_STONE then
        self:playStoneAlertEffect(STONE_TYPE_DARK);
    elseif state == AlertStateM.ALERT_STATE_GAS_DEVICE then
        self:playGasAlertEffect();
    elseif state == AlertStateM.ALERT_STATE_INTERFERE_DEVICE then
        self:playGasAlertEffect(getCommonIconPath("alert_blue"));
    end
end

-- 显示上面的菜单
function UIDungeonMain:showTop()
    self.dungeonTopMenu:setVisible(true);
end

-- 隐藏上面的菜单
function UIDungeonMain:hideTop()
    self.dungeonTopMenu:setVisible(false);
end

-- 显示冈布奥模型
function UIDungeonMain:showHeroModex()
    local headImg = findChildByName(self.node, "CT2/head_pic");
    local modelNode = findChildByName(self.node, "CT2/model_node");

    headImg:setVisible(headImg.initVisible);
    modelNode:setVisible(true);
end

-- 显示冈布奥模型
function UIDungeonMain:hideHeroModex()
    local headImg = findChildByName(self.node, "CT2/head_pic");
    local modelNode = findChildByName(self.node, "CT2/model_node");

    headImg.initVisible = headImg:isVisible();
    headImg:setVisible(false);
    modelNode:setVisible(false);
end

-- 显示下面的菜单
function UIDungeonMain:showMenu()
    local ct2 = findChildByName(self.node, "CT2");
    local ct3 = findChildByName(self.node, "CT3");
    ct2:setVisible(true);
    ct3:setVisible(true);
    self.dungeonTopMenu:setVisible(true);
end

-- 隐藏下面的菜单
function UIDungeonMain:hideMenu()
    local ct2 = findChildByName(self.node, "CT2");
    local ct3 = findChildByName(self.node, "CT3");
    ct2:setVisible(false);
    ct3:setVisible(false);
    self.dungeonTopMenu:setVisible(false);
end

-- 英雄说话
function UIDungeonMain:showHeroTalk(content)
    local talk = findChildByName(self.node, "CT2/talk");
    talk:setVisible(true);
    talk:setOpacity(255);

    local text = findChildByName(self.node, "CT2/talk/text");
    TextStyleM.setTextStyle(text, TextStyleM.TEXT_SIZE_SMALL, TextStyleM.TEXT_COLOR_DARK_BROWN);
    text:setString(content);

    autoResizeBg(text, talk, 50, nil, true);

    talk:stopAllActions();
    talk:runAction(cc.Sequence:create(cc.DelayTime:create(0.5), cc.FadeOut:create(2)));
end

-- 适配
resize = function(node)
    local w = AlignM.frameSize.width;
    local h = AlignM.frameSize.height;
    node:setContentSize(w, h);

    local scale;
    if h / w > 1.5 then
        scale = w / (DESIGN_WIDTH);
    else
        -- 在宽高比小于等于1.5时（如iphone或ipad），需要略微比预定比例缩小些。 所以加了75像素
        scale = h / (DESIGN_HEIGHT + 75);
    end

    local topBg = findChildByName(node, "CT1/top_bg");
    local bottomBg = findChildByName(node, "CT1/bottom_bg");
    local mask = findChildByName(node, "CT1/mask");
    local newWidth = w / scale;
    topBg:setScaleX(newWidth/topBg:getContentSize().width);
    bottomBg:setScaleX(newWidth/bottomBg:getContentSize().width);
    topBg:setScaleY(topBg:getScaleY());
    bottomBg:setScaleY(bottomBg:getScaleY());
    mask:setScaleX(newWidth/mask:getContentSize().width);

    local ct1 = findChildByName(node, "CT1");
    ct1:setPosition(w / 2, h / 2);
    ct1:setScale(scale);

    local scale2;
    if h / w >= 1.5 then
        scale2 = w / (DESIGN_WIDTH);
    else
        scale2 = h / (DESIGN_HEIGHT);
    end

    -- 底部菜单按原来适配规则缩放
    local ct2 = findChildByName(node, "CT2");
    ct2:setPosition(w / 2, 0);
    ct2:setScale(scale2);

    local ct3 = findChildByName(node, "CT3");
    ct3:setPosition(w / 2, 0);
    ct3:setScale(scale);

    local talkNode = findChildByName(node, "talk_node");
    talkNode:setPosition(w / 2, h / 2);
    talkNode:setScale(scale * DUNGEON_AJUST_SCALE);

    local effectNode = findChildByName(node, "effect_node");
    effectNode:setPosition(w / 2, h / 2);
    effectNode:setScale(scale * DUNGEON_AJUST_SCALE);

    local lockNode = findChildByName(node, "lock_panel");
    lockNode:setPosition(w / 2, h / 2);
    lockNode:setScale(scale * DUNGEON_AJUST_SCALE);

    local lockNode2 = findChildByName(node, "lock_panel2");
    lockNode2:setPosition(w / 2, h / 2);
    lockNode2:setContentSize(w + 10, h + 10);

    local slidePanel = findChildByName(node, "slide_touch_panel");
    slidePanel:setPosition(w / 2, h / 2);
    slidePanel:setScale(scale * DUNGEON_AJUST_SCALE);

    local specialEffectNode = findChildByName(node, "effect_ex_node");
    specialEffectNode:setPosition(w / 2, h / 2);
    specialEffectNode:setScale(scale * DUNGEON_AJUST_SCALE);

    local bossEffectNode = findChildByName(node, "boss_effect_node");
    bossEffectNode:setPosition(w / 2, h / 2);
    bossEffectNode:setScale(scale * DUNGEON_AJUST_SCALE);

    local selectTargetNode = findChildByName(node, "select_target_node");
    selectTargetNode:setPosition(w / 2, h / 2);
    selectTargetNode:setScale(scale);
    local panel = findChildByName(node, "select_target_node/panel");
    panel:setScale(DUNGEON_AJUST_SCALE);

    -- 目标选择蒙版拉伸充满整个屏幕
    local selectTargetBg = node:getChildByName("select_target_bg")
    selectTargetBg:setContentSize(w, h)
    selectTargetBg:setPosition(0, 0)

    -- 黑色蒙版拉伸充满整个屏幕
    local blackMaskBg = node:getChildByName("black_mask");
    blackMaskBg:setContentSize(w, h);
    blackMaskBg:setPosition(0, 0);

    AlignM.alignToTopCenter(node, "TOP");

    -- 能量圈和BUFF状态条节点适配，保持与顶部菜单固定距离
    local skillEnergyBarNode = findChildByName(node, "CT1/skill_energy_bar");
    local statusBarNode = findChildByName(node, "CT1/status_bar");
    if h / w > 1.5 then
        local deltaY = (h / w * DESIGN_WIDTH - (DESIGN_HEIGHT + 75)) / 2;
        skillEnergyBarNode:setPositionY(skillEnergyBarNode:getPositionY() + deltaY);
        statusBarNode:setPositionY(statusBarNode:getPositionY() + deltaY);
    end

    -- 计算最右边的点的坐标
    local pos = skillEnergyBarNode:getParent():convertToNodeSpace(cc.p(w, 0));
    -- 能量圈始终紧靠右边
    skillEnergyBarNode:setPositionX(pos.x);
end

-- 提示领取了矿脉奖励
function UIDungeonMain:hintMineBonus(args)
    local bonus = args.bonus;

    local msg  = "";
    local name;
    if bonus[1] == 1 then
        name = ItemM.query(bonus[2], "name");
        msg  = string.format(getLocStr("mine_bonus_taken"), name, bonus[3]);
    else
        name = FieldsM.getFieldName(bonus[2]);
        msg  = string.format(getLocStr("mine_bonus_taken"), name, bonus[3]);
    end

    -- 如果是最后一个奖励
    if args.isLastBonus == true then
        msg = msg .. "\n" .. getLocStr("mine_bonus_over");
    end

    showHint(msg);
end

-- 检查是否显示称号学习动画
function UIDungeonMain:checkCareerAnima()
    if self.checkCareerAnimaNode == nil then
        self.checkCareerAnimaNode = cc.Node:create();
        self:addChild(self.checkCareerAnimaNode);
    end

    local function doCheckCareerAnima()
        local bubble = findChildByName(self.node, "CT2/career_bubble");

        -- 检查是否有可学习或升级的称号
        local careerId = CareerM.checkCareer();
        local careerInfo = CareerM.getCareerInfo(careerId);
        if not careerId or not DungeonGuideM.isGuideFinished() then
            -- 不满足学习或升级的条件、或处于新手关卡引导，隐藏动画
            -- 将头像背景替换为暗色的
            bubble:setVisible(false);
            return;
        end

        bubble:setVisible(true);
        bubble:stopAllActions();
        bubble:setScale(1);
        playBubbleSlightAnima(bubble, 0.1);
    end
    self.checkCareerAnimaNode:stopAllActions();
    performWithDelay(self.checkCareerAnimaNode, doCheckCareerAnima, 0.05);
end

-- 如果有生命流失需要播放
function UIDungeonMain:playDelayHpEffect()
    -- 如果有生命流失需要播放
    if ME.user.dbase:queryTemp("delayHpEffect") then
        local params = { ["damage"] = ME.user.dbase:queryTemp("delayHpEffect"), };
        -- 播放玩家受创
        self:playHeroDamaged(params);
        ME.user.dbase:deleteTemp("delayHpEffect");
        self:updateUIByDesc("hp");

        -- 检查下是否需要播放重生特效
        if ME.user.dbase:queryTemp("delayRevive") then
            EventMgr.fire(event.HOLY_REVIVE_EFFECT);
            ME.user.dbase:deleteTemp("delayRevive");
        end
    end
end

-- 处理进入下一层时获得的属性提升效果
function UIDungeonMain:handleNextFloorGainBonus(args)
    -- 获取进入下一层所获得的奖励
    local bonusList = table.deepcopy(BonusM.getBonusCollect());
    local para = args or {};

    -- 整理下奖励, 相同奖励一起
    local bonusInfo = {};
    if para.arrange == 1 then

        -- 适用用于奖励大部分相同的情况
        for k, v in pairs(bonusList) do
            local isNew = true;

            for _, bonusTemp in pairs(bonusInfo) do
                if bonusTemp[1] == v[1] and bonusTemp[2] == v[2] then
                    bonusTemp[3] = bonusTemp[3] + v[3];
                    isNew = false;
                    break;
                end
            end

            if isNew == true then
                table.insert(bonusInfo, v);
            end

        end

        bonusList = bonusInfo;
    end

    BonusM.clearBonusCollect();
    if #bonusList == 0 then
        self:playDelayHpEffect();
        return;
    end

    -- 显示选择目标层， 用来屏蔽界面操作
    local node = findChildByName(self.node, "select_target_bg");
    node:setVisible(true);
    node:setOpacity(0);

    -- 处理奖励
    for _, bonus in pairs(bonusList) do
        -- 物品，不处理龙珠
        if bonus[1] == 1 then
            local itemId = bonus[2];
            if not DragonWishM.isWishItem(itemId) then
                local num = bonus[3];
                if SpellM.isSpell(itemId) then
                    playSpellGainEffectOnEnter(self:getSpellNode(), self:getHeroPos(), itemId, num);
                elseif EquipM.isEquipment(itemId) or PropertyM.isProperty(itemId) then
                    playEquipGainEffectOnEnter(self:getEquipNode(), self:getHeroPos(), itemId, num);
                else
                    playUsePropertyGetItem(bonus);
                end
            end
        elseif bonus[1] == 2 then -- 属性
            local effectNode = self:getEffectParent();
            if bonus[2] == "hp" or bonus[2] == "max_hp" or bonus[2] == "limit_hp" then
                -- 需要处理下奖励数值
                local count = bonus[3];
                playHpGainEffectOnEnter(effectNode, self:getHeroPos(), self:getHpTextNode(), self:getHpIconNode(), count);

            elseif bonus[2] == "mp" or bonus[2] == "max_mp" or bonus[2] == "limit_mp" then
                -- 需要处理下奖励数值
                local count = bonus[3];
                playMpGainEffectOnEnter(effectNode, self:getHeroPos(), self:getMpTextNode(), self:getMpIconNode(), count);
            elseif bonus[2] == "attack" then
                local count = bonus[3];
                playAttackGainEffectOnEnter(effectNode, self:getHeroPos(), self:getAttackTextNode(), self:getAttackIconNode(), count);
                performWithDelay(self, function() self:updateUIByDesc("attack"); end, 1.5);
            elseif bonus[2] == "magic" then
                local count = bonus[3];
                playMagicGainEffectOnEnter(effectNode, self:getHeroPos(), self:getMagicTextNode(), self:getMagicIconNode(), count);
                performWithDelay(self, function() self:updateUIByDesc("magic"); end, 1.5);
            elseif bonus[2] == "search_point" then
                playSpGainEffectOnEnter(effectNode, self:getHeroPos(), self.dungeonTopMenu:getSpTextNode(), self.dungeonTopMenu:getSpIconNode(), bonus[3]);
            else
                playUsePropertyGetItem(bonus);
            end
        end
    end

    if #bonusList > 0 then
        local heroPos = self:getHeroPos();
        playEffect(self:getEffectParent(), 1158, heroPos.x, heroPos.y);
    end
    -- 播放生命流失
    self:playDelayHpEffect();

    local function done()
        -- 允许操作
        node:setVisible(false);
        node:setOpacity(255);
    end
    performWithDelay(self, done, 1);
end

-- 执行缓存的格子转换事件
function UIDungeonMain:doCacheTransform(delay)
    if not self.cacheTransformGrids or #self.cacheTransformGrids <= 0 then
        -- 缓存已经清空了，不处理
        return;
    end

    -- 延迟播放时间
    delay = delay or 0;

    local i = 0;
    local interval = 0.1;  -- 刷新间隔

    repeat
        local function refresh(pos)
            -- 采用静默方式打开格子
            local grid = self.grids[pos];
            grid:onTransformGrid(pos);
            self:openGrid(pos, grid.type, true);

            self:activeDisplayGrid(pos);
        end

        -- 随机从缓存列表中取一个位置刷新
        local index = math.random(#self.cacheTransformGrids);
        local pos = self.cacheTransformGrids[index];
        table.remove(self.cacheTransformGrids, index);

        local gridData = self.grids[pos].gridData;
        local refreshTime = i * interval + delay;
        if  gridData.type == GRID_TYPE_SUMMON and
            SummonM.query(gridData.class_id, "delay") ~= 1 then
            refreshTime = 0;
        end

        performWithDelay(self, function() refresh(pos) end, refreshTime);
        i = i + 1;
    until #self.cacheTransformGrids <= 0
end

-- 执行缓存的开格子事件
-- info是一个键值对。传入的格式：{[pos]=delay,}
-- 其中pos:位置，delay:等待时间
function UIDungeonMain:doCacheGridOpen(interval, extra)
    if not self.cacheOpenGrids or #self.cacheOpenGrids <= 0 then
        -- 缓存已经清空，不处理
        return;
    end

    -- 间隔
    interval = interval or 0.1;
    local i = 0;

    repeat
        local info = self.cacheOpenGrids[1];
        local pos     = info.pos;
        local gridType    = info.type;
        local isQuite = info.isQuite;

        local delayTime = i * interval;
        if type(extra) == "table" and type(extra["delayInfo"]) == "table" then
            delayTime = extra["delayInfo"][pos] or delayTime;
        end

        table.remove(self.cacheOpenGrids, 1);

        local function open(args)
            -- 判断当前地牢数据是否合法
            if not DungeonM.getCurrentDungeon() then
                return;
            end

            -- 打开格子
            self:openGrid(pos, gridType, isQuite);

            -- 如果翻开的是怪物，需要刷新一下所有已翻开怪物的属性
            local grid = DungeonM.getGridByPos(pos);
            if grid:isMonster() then
                -- 刷新所有已存在怪物的属性
                for _, g in pairs(self.grids) do
                    g:updateHPAndAttack();
                end
            end

            -- 抛出开格子事件
            EventMgr.fire(event.UI_GRID_OPEN, { ["pos"] = pos, ["type"] = gridType, ["isQuite"] = isQuite, });

            return 0.5;
        end

        local gridNode = self.grids[pos];
        local function delay(args)
            return args.duration;
        end
        gridNode.actionManager:addAction(gridNode.gridData, delay, "openDelay", { duration = delayTime, }, GRID_ACTION_PHASE_GRID_OPENED);
        gridNode.actionManager:addAction(gridNode.gridData, open, "open", { }, GRID_ACTION_PHASE_GRID_OPENED);

        i = i + 1;

    until #self.cacheOpenGrids <= 0
end

-- 发动陷阱的回调
function UIDungeonMain:whenLaunchTrap(args)
    local pos     = args.pos
    local trapId  = args.trapId;
    local targets = args.targets or {};
    local sourceNode = self.grids[pos];

    local trapType  = CommonTrapM.query(trapId, "type");
    local trapAlias = CommonTrapM.query(trapId, "alias");
    local rule = CommonTrapM.query(trapId, "rule");
    local feature = CommonTrapM.query(trapId, "feature");

    -- 如果需要触发特技发动事件
    local dbase= CommonTrapM.query(trapId, "dbase");
    if dbase["skill_trigger"] then
        EventMgr.fire(event.SPECIAL_SKILL_TRIGGER, { propId = dbase["skill_trigger"][1], key = dbase["skill_trigger"][2],});
    end

    -- 根据陷阱类型播放不同的效果
    if rule == "beeline_trap" then
        -- 直线型陷阱

        local targetNum = #targets;
        local targetNode;
        local targetPos;
        if targetNum == 0 then
            -- 该方向上没有目标，取最远的格子
            local direction = sourceNode.gridData.direction;
            targetPos = CommonTrapM.getFarmostGridPos(pos, direction);

        else
            if feature == TRAP_FEATURE_NOT_THROUGH then
                -- 不可穿透类型的，取最近的目标
                local nearestTarget = targets[1];
                targetPos = nearestTarget:getPos();
            else
                -- 取最远的目标
                local farmostTarget = targets[targetNum];
                targetPos = farmostTarget:getPos();
            end
        end

        targetNode = self.grids[targetPos];

        -- 播放特效
        if trapAlias == "magic_bow" then
            -- 破魔重弩
            AudioM.playFx("tiro_crossbow");
            crossbowTrapEffect(sourceNode, targetNode);
        else
            AudioM.playFx("archer_launch");
            beelineTrapEffect(sourceNode, targetNode, args.direction);
        end
    elseif trapType == TRAP_TYPE_CROSS then
        -- 十字陷阱

        local dirList = CommonTrapM.getDirections();
        local posInfo = CommonTrapM.fetchDirectionPos(pos, targets);
        local targetNodes = {};
        for _, direction in pairs(dirList) do
            -- 找到各个方向上的目标节点
            if type(posInfo[direction]) == 'table' then
                targetNodes[direction] = {};

                -- 获取炮口所指方向上的所有格子
                local targets = CommonTrapM.getDirGrids(pos, direction);
                for _, grid in pairs(targets) do
                    table.insert(targetNodes[direction], self.grids[grid:getPos()]);
                end
            end
        end

        if trapAlias == "gun_trap" then
            -- 土制炮台
            cropTrapEffect(sourceNode, targetNodes);
        elseif trapAlias == "fire_blade" then
            -- 炎龙刃
            playFireBladeEffect(sourceNode, targetNodes);
        end

    elseif trapAlias == "element_swirl" then
        -- 元素漩涡
        -- 播放元素漩涡消失特效
        local targetNodes = {};

        for _, grid in ipairs(targets) do
            if not grid:isMonster() or grid.monster:isDead() then
                table.insert(targetNodes, self.grids[grid:getPos()]);
            end
        end

        playElemSwirlDisappear(sourceNode, targetNodes);

    elseif trapAlias == "ship_gun" then
        -- 排炮

        local targetNodes = {};
        local direction = sourceNode.gridData.direction;
        if #targets == 0 then
            -- 炮口所指方向上没有目标，直接取最远的格子发射
            local farPos = CommonTrapM.getFarmostGridPos(pos, direction);
            if farPos ~= pos then
                -- 如果炮台在该方向上最远处，则该方向的炮口无效
                table.insert(targetNodes, self.grids[farPos]);
            end
        else
            for _, grid in pairs(targets) do
                table.insert(targetNodes, self.grids[grid:getPos()]);
            end
        end

        playCannonAttackEffect(sourceNode, targetNodes);
    elseif trapAlias == "blood_trap" then
        local targetNodes = {};

        for _, grid in ipairs(targets) do
            if grid:isOpened() then
                if not grid:isMonster() or grid.monster:isDead() then
                    table.insert(targetNodes, self.grids[grid:getPos()]);
                end
            end
        end

        -- 血腥陷阱
        playBloodTrapEffect(sourceNode, targetNodes);

    elseif trapAlias == "ship_gun2" then
        -- 排炮2
        local targetNodes = {};
        local direction = sourceNode.gridData.direction;
        if #targets == 0 then
            -- 炮口所指方向上没有目标，直接取最远的格子发射
            local farPos = CommonTrapM.getFarmostGridPos(pos, direction);
            if farPos ~= pos then
                -- 如果炮台在该方向上最远处，则该方向的炮口无效
                table.insert(targetNodes, self.grids[farPos]);
            end
        else
            for _, grid in pairs(targets) do
                table.insert(targetNodes, self.grids[grid:getPos()]);
            end
        end

        playCannonAttackEffect(sourceNode, targetNodes);

    elseif trapAlias == "time_bomb" then
        local targetNodes = {};

        for _, grid in ipairs(targets) do
            if grid:isOpened() then
                if not grid:isMonster() or grid.monster:isDead() then
                    table.insert(targetNodes, self.grids[grid:getPos()]);
                end
            end
        end

        -- TODO： 定时炸弹爆炸，先随便弄个暂代
        playTimeBombEffect(sourceNode, targetNodes);
    end
end

-- 英雄施法
function UIDungeonMain:whenApplyMagic(skillId)
    local shake = SkillM.query(skillId, "shake");

    -- 英雄喊话
    local skillName = SkillM.query(skillId, "name").."!!";
    self:showHeroTalk(skillName);

    if shake ~= nil and tonumber(shake) == 1 then
        --震动下
        self:setPosition(self.standardPos);
        playShakeEffect(self, 0.03, 8, 8, self.standardPos);
    end

    -- 玩家施法获得属性奖励，需要等动画播完后再刷新界面，提前return
    if ME.user.dbase:queryTemp("magic_bonus_delay") == 1 then
        ME.user.dbase:deleteTemp("magic_bonus_delay");
        return;
    end

    self:updateUI();
end

-- 尝试触发神龙许愿界面
function UIDungeonMain:tryOpenWishForm()
    if type(self.cacheWishOptions) ~= 'table' or
       #self.cacheWishOptions == 0 then
        -- 没有缓存的选项，不处理
        return;
    end

    local options = self.cacheWishOptions;

    -- 显示神龙许愿界面
    UIMgr.getCurrentScene():removeFormByName("UIDragonWish");
    local uiDragonWish = UIDragonWish.create(options);
    UIMgr.getCurrentScene():addForm(uiDragonWish);

    -- 清除缓存的选项
    self.cacheWishOptions = {};
end

-- 显示石柱警戒
function UIDungeonMain:playStoneAlertEffect(stoneType)
    local curScene = UIMgr.getCurrentScene();
    curScene:removeFormByName("UIStoneAlert");
    local uiForm = UIStoneAlert.create(stoneType);
    curScene:addForm(uiForm);
    curScene.haveStoneAlert = true;
end

-- 移除石柱警戒
function UIDungeonMain:stopStoneAlertEffect()
    local curScene = UIMgr.getCurrentScene();

   if curScene.haveStoneAlert then
        local function remove()
            if not curScene.haveStoneAlert then
                curScene:removeFormByName("UIStoneAlert");
            end
        end
        performWithDelay(curScene, remove, 0.01);

       curScene.haveStoneAlert  = false;
   end
end

-- 停在所有预警特效
function UIDungeonMain:stopAllAlertEffect()
    -- 关闭血量预警
    self:stopHPAlertEffect();

    -- 移除石柱警戒
    self:stopStoneAlertEffect();

    -- 移除毒气装置警戒
    self:stopGasAlertEffect();
end

-- 初始化新手关卡引导
function UIDungeonMain:initGuide()
    -- 引导已完成，或者该地图不是第一小关的第一层，直接返回
    if DungeonGuideM.isGuideFinished() or  not (DungeonM.getDungeonId() == 11 and DungeonM.currentLayer() == 1) then
        return;
    end

    self.guideMgr = UIDungeonGuideMgr.create();

    -- 开始引导
    self.guideMgr:start();
end

-- 初始化怪物BUFF光效模块
function UIDungeonMain:initMonsterBuffEffect()
    self.monsterBuffEffectM = MonsterBuffEffectM.create();
    -- 以一个不可见节点存在
    self:addChild(self.monsterBuffEffectM);
end

-- 一个回合事件的回调
function UIDungeonMain:whenCombatRound()
    -- 属性可能发生变化了，刷新一下界面
    self:updateCD();
    self:updateUI();
    self:updateHPAndAttack();
end

-- 迷宫坍塌
function UIDungeonMain:dungeonCollapse()
    local imgNode = self:getBlackMask();

    -- 初始化黑色蒙版属性
    imgNode:setVisible(true);
    imgNode:setOpacity(0);
    imgNode:loadTexture("images/ui/common/mengban_black.png");
    imgNode:setCascadeOpacityEnabled(true);
    imgNode:setTouchEnabled(true);

    if self.uiMsgReminder then
        self.uiMsgReminder:setVisible(false);
    end

    -- 图片渐入渐出
    local fadeIn = cc.FadeTo:create(2, 255);

    local delay = cc.DelayTime:create(2);

    local callFunc1 = cc.CallFunc:create(function()
        alert(getLocStr("dungeon_collapse"));
    end);

    local callFunc2 = cc.CallFunc:create(function()
        DungeonActionM.go("leave_dungeon");
    end);

    playShakeEffect(UIDungeonMgr.getCurLevel(), 0.03, 4, 15);

    imgNode:runAction(cc.Sequence:create(fadeIn, callFunc1, delay, callFunc2));
end

-- 更新倒计时
function UIDungeonMain:updateCD()
    for _, grid in pairs(self.grids) do
        -- 倒计时
        grid:updateCountDown();
    end

    if ME.user.ship then
        -- 幽灵船技能气泡
        local pos = ME.user.ship:getPos();
        local grid = self.grids[pos];

        if grid.gridData.type == GRID_TYPE_GHOST_SHIP then
            grid:redrawShipSkill();
        end
    elseif ME.user.bishop then
        -- 主教技能气泡
        local pos = ME.user.bishop:getPos();
        local grid = self.grids[pos];

        if grid.gridData.type == GRID_TYPE_BISHOP then
            grid:redrawBishopSkill()
        end
    end
end

-- 处理吸能术效果
function UIDungeonMain:handleAbsorbEffect(grid)
    -- 吸能术效果
    if self.gainImage.propId == 513 and self.gainImage.targetRound ~= nil and self.gainImage.targetRound - CombatM.getRound() >= 0 then
        -- 吸能术播放特殊效果
        playAbsorbEffect(grid);
    end
end

-- 初始化技能能量槽
function UIDungeonMain:initSkillEnergyBar()
    self.skillEnergyBarList = {};

    -- 获取拥有主动技能的冈布奥列表
    local slimeSkillList = {};
    local props = PropM.fetchProps(ME.user, "gain_slime_skill");
    for _, prop in ipairs(props) do
        local id = tonumber(prop[2]);
        local petId = SlimeSkillM.query(id, "pet_id");

        if table.indexOf(slimeSkillList, petId) < 0 then
            table.insert(slimeSkillList, petId);
        end
    end

    -- 当前迷宫内冈布奥列表
    local dungeonPetList = PetM.getDungeonPetList();
    for _, id in pairs(dungeonPetList) do
        if (PetM.isSpecialPet(id) or FormulaM.invoke("GET_SPECIAL_SLIME_SKILL", id))
            and table.indexOf(slimeSkillList, id) < 0 then
            table.insert(slimeSkillList, id);
        end
    end

    local haveDragonMagic = DungeonM.getDungeonMixedField("dragon_magic") or 0;

    -- 如果有龙语魔法应继续
    if #slimeSkillList == 0 and haveDragonMagic ~= 1 then
        return;
    end

    local skillEnergyBarNode = findChildByName(self.node, "CT1/skill_energy_bar");
    skillEnergyBarNode:setCascadeOpacityEnabled(true);

    local x = -40;
    for _, petId in pairs(slimeSkillList) do
        if petId ~= nil and petId ~= "" then
            local bar = UISkillEnergyBar.create(petId);
            skillEnergyBarNode:addChild(bar);
            bar:setPositionX(x);

            x = x - 65;

            table.insert(self.skillEnergyBarList, bar);
        end
    end

    -- 最后加上龙语魔法
    if haveDragonMagic == 1 then
        --
        local DRAGON_MAGIC = -1;
        local bar = UISkillEnergyBar.create(DRAGON_MAGIC);
        skillEnergyBarNode:addChild(bar);
        bar:setPositionX(x);

        x = x - 65;
    end

end

-- 获得玩家身上携带的物品
function UIDungeonMain:getOwnedItems(spAmount)
    -- 获取玩家当前身上的神器
    local artifactId = ME.user:getCarryArtifact();
    local itemsList = {};

    if artifactId ~= 0 then
        local ret = {1, artifactId, 1};
        table.insert(itemsList, ret);
    end

    -- 获取玩家身上的特殊宝物
    local treasureIds = ItemM.getItemsByType(ME.user, ITEM_TYPE_SPECIAL_TREASURE);
    for _, id in pairs(treasureIds) do
        local amount = ItemM.getAmount(ME.user, id);
        local ret = {1, id, amount};
        table.insert(itemsList, ret);
        if amount > 0 then
            self:autoEquip(id);
        end
    end

    -- 获得玩家身上的宝物
    treasureIds = ItemM.getItemsByType(ME.user, ITEM_TYPE_TREASURE);
    for _, id in pairs(treasureIds) do
        local amount = ItemM.getAmount(ME.user, id);
        local ret = {1, id, amount};
        table.insert(itemsList, ret);
        if amount > 0 then
            self:autoEquip(id);
        end
    end

    -- 获取玩家身上的可使用物品
    local propertyIds = ItemM.getItemsByGroup(ME.user, 23);
    for _, id in pairs(propertyIds) do
        local amount = ItemM.getAmount(ME.user, id);
        local ret = {1, id, amount};
        table.insert(itemsList, ret);
    end

    -- 获取玩家当前身上可携带的试剂
    local dungeonItem = ME.user.dbase:query("dungeon_item", {});
    for key, value in pairs(dungeonItem) do
       if value > 0 then
           local ret = {1, key, value};
            table.insert(itemsList, ret);
       end
    end

    -- 获得玩家身上的卷轴
    local spellIds = ItemM.getItemsByType(ME.user, ITEM_TYPE_SPELL);
    for _, id in pairs(spellIds) do
        local amount = ItemM.getAmount(ME.user, id);
        local ret = {1, id, amount};
        table.insert(itemsList, ret);
    end

    -- 初始化探索点
    local sp = spAmount;
    local ret = {2, "search_point", sp};
    table.insert(itemsList, ret);

    -- 播放初始携带的物品动画
    self:playItemsAnimation(itemsList);
end

-- 播放携带的物品奖励
function UIDungeonMain:playItemsAnimation(itemsList, delay)
    local bonusList = itemsList;

    if #bonusList == 0 then
        return;
    end

    -- 处理奖励
    local n = 0;
    delay = delay or 1;
    for _, bonus in pairs(bonusList) do
        local function playEffect()
        -- 物品
            if bonus[1] == 1 then
                local itemId = bonus[2];
                local num = bonus[3];
                if SpellM.isSpell(itemId) then
                    playSpellGainEffectOnEnter(self:getSpellNode(), self:getHeroPos(), itemId, num);
                else
                    playEquipGainEffectOnEnter(self:getEquipNode(), self:getHeroPos(), itemId, num);
                end

            elseif bonus[1] == 2 then -- 属性
                local effectNode = self:getEffectParent();
                if bonus[2] == "search_point" then
                    playSpInitEffect(effectNode, self:getHeroPos(), self.dungeonTopMenu:getSpTextNode(), self.dungeonTopMenu:getSpIconNode(), bonus[3]);
                end
           end
        end

        -- 延迟播放
        performWithDelay(self, playEffect, delay*n)
        n = n + 1;
    end

    if #bonusList > 0 then
        local heroPos = self:getHeroPos();
        playEffect(self:getEffectParent(), 1158, heroPos.x, heroPos.y);
    end
end

-- 进入下一层的回调
function UIDungeonMain:whenNextFloor()
    -- 检查是有特技发动
    local propId = ME.user.dbase:queryTemp("speicial_skill_trigger") or 0;
    if propId > 0 then
        EventMgr.fire(event.SPECIAL_SKILL_TRIGGER, { propId = propId });
        ME.user.dbase:deleteTemp("speicial_skill_trigger");
    end

    -- 检查是否需要发言
    local talkInfo = ME.user.dbase:queryTemp("hero_talk");
    if talkInfo and talkInfo["trans_times"] then
        EventMgr.fire(event.HERACLES_TALK, talkInfo);
    end

    -- 检查是否有特效需要播放
    local stoneInfo = ME.user.dbase:queryTemp("boss_stone_effect");
    if stoneInfo then
        -- 抛出切换状态事件
        performWithDelay(self, function()
            EventMgr.fire(event.UPDATE_STONE_STATUS, { ["pos"] = stoneInfo.pos, ["status"] = stoneInfo.status, ["id"] = stoneInfo.id, });
        end, 6.8);

        -- 删除标记
        ME.user.dbase:deleteTemp("boss_stone_effect");
    end

    -- 阿瓦隆巨炮
    local avalonCannon = AvalonFortM.findAvalonCannon();
    if avalonCannon then
        -- 生成阿瓦隆巨炮，因为层级问题，所以在这里生成
        playAvalonCannonAnimation(avalonCannon:getPos());

        -- 阿瓦隆巨炮自动装填弹药
        if AvalonCannonM.queryCannonPlayLoadEffect() then
            -- 抛出自动装填弹药的事件
            EventMgr.fire(event.AVALON_CANNON_LOAD_AMMUNITION, {["pos"] = avalonCannon:getPos(),});

            -- 标记已播放
            AvalonCannonM.setCannonLoadEffect(false);
        end
    end

    -- 毒气装置警戒
    local grid = AvalonFortM.findGsaDevice();
    if grid and grid.state ~= GRID_STATE_DISABLE then
        AlertStateM.setState(AlertStateM.ALERT_STATE_GAS_DEVICE);

        -- 生成毒气装置，因为层级问题，所以在这里生成
        playGasDeviceAnimation(grid.pos);
    end

    -- 冰霜泰坦护盾
    if ME.user.dbase:queryTemp("ice_titant_shield") == 1 then
        local pos = DungeonBossM.getBossPos();
        if pos ~= -1 then
            performWithDelay(self, function()
                -- 护盾光效
                local grid = self.grids[pos];
                playEffect(grid.node, 1523);

                -- 警戒效果
                AlertStateM.setState(AlertStateM.ALERT_STATE_INTERFERE_DEVICE);

                ME.user.dbase:deleteTemp("ice_titant_shield");
            end, 6.8);
        end
    end

    -- 获取当前所在的迷宫编号和层数
    local dungeonId = DungeonM.getDungeonId();
    local layer = DungeonM.currentLayer();

    for _, taskInfo in ipairs(DailyTaskM.getTaskList()) do
        if taskInfo.dungeon_id == dungeonId and taskInfo.floor == layer then
            if not taskInfo.reminded and taskInfo.is_finish == 0 then

                -- 先标记已提示
                DailyTaskM.reminded(taskInfo.id);

                -- 根据任务类型弹出提示框
                local taskId = taskInfo.id;
                local taskType = DailyTaskM.query(taskId, "type");
                local title = getLocStr("circus_title");
                if taskType == "find_item" then
                    -- 提示发现丢失的道具
                    local msg = string.format(getLocStr("daily_task_notice1"), ItemM.query(DailyTaskM.query(taskId, "task_item"), "name"));

                    confirm2(CONFIRM_TYPE_OK, title, msg);
                    AudioM.playFx("button_spell");
                elseif taskType == "snares_animal" then
                    -- 提示发现逃走的动物
                    local msg = string.format(getLocStr("daily_task_notice2"), MonsterM.query(DailyTaskM.query(taskId, "task_monster"), "name"));

                    confirm2(CONFIRM_TYPE_OK, title, msg);
                    AudioM.playFx("button_spell");
                elseif taskType == "inherit" then
                    local activePet = ME.user.dbase:query("active_pet", 0);
                    local campId = CampM.getPetCamp(activePet);
                    if campId == taskInfo.camp_id then

                        local function showDialog()
                            -- 提示获得认可
                            local function callback()
                                -- 尝试完成任务
                                -- 不能放在UI做，DailyTaskM中处理
                                -- DailyTaskM.tryToFinishTask(taskId);
                            end

                            local extraData = { ["sub_name"] = DailyTaskM.query(taskId, "name"), ["frame_icon"] = DailyTaskM.query(taskId, "icon"), ["cancel_callback"] = callback};
                            local uiForm = UIFeatureDesc.create(500, nil, callback, extraData);
                            UIMgr.getCurrentScene():addForm(uiForm);
                        end

                        local msgList = {string.format(getLocStr("daily_task_inherit_text"), DailyTaskM.query(taskId, "name")), };
                        local UIHeroSpeak = UIHeroSpeak.create(self, activePet, msgList, "B_FLY_IN", "B_FLY_OUT", nil, showDialog);
                        AudioM.playFx("slime_swordman_app");
                        UIMgr.getCurrentScene():addForm(UIHeroSpeak);
                    end
                end
            end
        end
    end

    -- 创建完毕回调
    for _, func in pairs(alreadyCallback) do
        func();
    end
    -- 清空
    alreadyCallback = {};
end

-- 拾取物品的回调
function UIDungeonMain:whenPickUpItem(args)
    local dungeonId = DungeonM.getDungeonId();
    local layer = DungeonM.currentLayer();

    for _, taskInfo in ipairs(DailyTaskM.getTaskList()) do
        if taskInfo.dungeon_id == dungeonId and taskInfo.floor == layer then
            local taskId = taskInfo.id;
            if DailyTaskM.query(taskId, "task_item") == args.bonus[2] then
                local function showDialog()
                    -- 拾取到任务需要的物品了
                    local extraData = { ["sub_name"] = DailyTaskM.query(taskId, "name"), ["frame_icon"] = DailyTaskM.query(taskId, "icon"), };
                    local uiForm = UIFeatureDesc.create(500, nil, nil, extraData);
                    UIMgr.getCurrentScene():addForm(uiForm);
                end

                local function callback()
                    local activePet = ME.user.dbase:query("active_pet");
                    local msgList = {string.format(getLocStr("daily_task_find_item_text"), ItemM.query(args.bonus[2], "name")), };
                    local UIHeroSpeak = UIHeroSpeak.create(self, activePet, msgList, "B_FLY_IN", "B_FLY_OUT", nil, showDialog);
                    UIMgr.getCurrentScene():addForm(UIHeroSpeak);
                end

                performWithDelay(self, callback, 0.6);

                -- 尝试完成任务
                -- 不能放在UI做，DailyTaskM中处理
                -- DailyTaskM.tryToFinishTask(taskId);
            end
        end
    end
end

-- 怪物死亡的事件
function UIDungeonMain:whenMonsterDie(args)
    local monster = args.monster;
    if not monster then
        return;
    end

    -- 触发逻辑层怪物死亡的回调
    local pos = args.pos;
    local grid = self.grids[pos];
    grid:onLogicMonsterDie();

    local classId = monster.classId;
    local dungeonId = DungeonM.getDungeonId();
    local layer = DungeonM.currentLayer();

    for _, taskInfo in ipairs(DailyTaskM.getTaskList()) do
        if taskInfo.dungeon_id == dungeonId and taskInfo.floor == layer then
            local taskId = taskInfo.id;
            if DailyTaskM.query(taskId, "task_monster") == classId then
                -- 音效
                AudioM.playFx("slime_swordman_app");
                local function callback( ... )
                    -- 击杀任务怪
                    local extraData = { ["sub_name"] = DailyTaskM.query(taskId, "name"), ["frame_icon"] = DailyTaskM.query(taskId, "icon"), };
                    local uiForm = UIFeatureDesc.create(500, nil, nil, extraData);
                    UIMgr.getCurrentScene():addForm(uiForm);
                end

                -- 尝试完成任务
                -- 不能放在UI做，DailyTaskM中处理
                -- DailyTaskM.tryToFinishTask(taskId);

                local activePet = ME.user.dbase:query("active_pet");
                local msgList = {string.format(getLocStr("daily_task_snares_animal_text"), DailyTaskM.query(taskId, "name")), };
                local UIHeroSpeak = UIHeroSpeak.create(self, activePet, msgList, "B_FLY_IN", "B_FLY_OUT", nil, callback);
                UIMgr.getCurrentScene():addForm(UIHeroSpeak);
            end
        end
    end
end

-- 随机发言
function UIDungeonMain:doRandTalk(posList)
    if type(posList) ~= 'table' or #posList == 0 then
        return;
    end

    -- 随机挑选几个位置发言
    local waitList = table.deepcopy(posList);
    local talkNum = 4;
    for i = 1, talkNum do
        if #waitList == 0 then
            break;
        end

        local index = math.random(#waitList);
        local pos = waitList[index];
        table.remove(waitList, index);
        local grid = self.grids[pos];
        grid:doRoleTalk();
    end

    -- 发言间隔
    local talkInterval = 4.0;
    performWithDelay(self, function() self:doRandTalk(posList) end, talkInterval);
end

-- 怪物收集能量
function UIDungeonMain:whenCollectEnergy(args)
     local amount   = args.amount;
     local pos      = args.pos;
     local attrib   = args.attrib;
     local grid     = self.grids[pos];

     if attrib == "energy" then
        grid:playCollectEnergy(amount);
    end
 end

-- 怪物消耗能量
function UIDungeonMain:whenCostEnergy(args)
     local amount   = args.amount;
     local pos      = args.pos;
     local attrib   = args.attrib;
     local grid     = self.grids[pos];

     if attrib == "energy" then
        grid:playCostEnergy(amount);
    end
end

-- 添加场景行为
function UIDungeonMain:whenAddSceneSequence(param)
    local action  = param.action;
    local fromPos = param.fromPos;
    local toPos   = param.toPos;
    local args    = param.args;

    -- 打印下
    printTable(param, "whenAddSceneSequence, param");

    -- 如果还没创建过，则新建一个
    if not self.sceneSequence then
        self.sceneSequence = SceneSequence.new();
    end

    -- 添加行为序列
    self.sceneSequence:addAction(action, fromPos, toPos, args);

    -- 尝试开始行为序列
    self:tryToPlaySceneSequence();
end

-- 尝试播放场景动画
function UIDungeonMain:tryToPlaySceneSequence()
    -- 播放动作序列
    local function playActions()

        if not self.sceneSequence:isEmpty() then
            self.scenePlaying = true;
        else
            self.scenePlaying = false;
        end

        while not self.sceneSequence:isEmpty() do
            local delay = self.sceneSequence:play();

            if delay == nil or delay <= 0 then
                playActions();
            else
                performWithDelay(self, playActions, delay);
                return;
            end
        end
    end

    if not self.scenePlaying then
        playActions();
    end
end

-- 连接消息管理界面
function UIDungeonMain:updateMsgReminderUI()
    if not self.uiMsgReminder then
        self.uiMsgReminder = UIMsgReminder.create();
        local msgReminderNode = findChildByName(self.node, "msg_reminder");
        msgReminderNode:addChild(self.uiMsgReminder);
    end

    self.uiMsgReminder:update();
end

-- 合成提醒
function UIDungeonMain:whenCompoundRemind(rule)
    if not rule then
        return;
    end

    local pet = ME.user:getActivePet();
    if not pet then
        return;
    end

    local product = CompoundM.query(rule, "product");
    if type(product) ~= "table" or #product == 0 then
        return;
    end

    local productId = product[1][2];

    -- 稍微延迟一下，等物品飞入包裹后再弹出窗口
    local function showCompoundForm()
        if DungeonM.isInDungeon() then
            -- 显示合成界面
            closeEquipUI();
            UIMgr.getCurrentScene():removeFormByName("UIItemCompound");
            local UIItemCompound = UIItemCompound.create(pet.classId, productId);
            UIMgr.getCurrentScene():addForm(UIItemCompound);
        end
    end
    performWithDelay(self, showCompoundForm, 2);
end

function UIDungeonMain:playEffect(parent, path, speed, callback, scale, offset)
    parent:setVisible(true);

    local effect, animAction = AnimationCacheM.createAnimationNodeAndAction(path);

    -- 设置速度
    if speed ~= nil then
        animAction:setTimeSpeed(speed);
    end

    -- 设置缩放
    if scale ~= nil then
        effect:setScale(scale);
    end

    effect:runAction(animAction);
    animAction:gotoFrameAndPlay(1, 60, false);

    local callfunc = cc.CallFunc:create(function()
        parent:removeChild(effect);
        AnimationCacheM.freeAnimationNodeAndAction(path, effect, animAction);
        if callback ~= nil then
            callback();
        end
    end);

    effect:runAction(cc.Sequence:create(cc.DelayTime:create(0.5), callfunc));
    parent:addChild(effect);

    -- 设置偏移
    if offset ~= nil then
        effect:setPosition(offset);
    end
end

-- 锁屏一段时间
function UIDungeonMain:lockScreen(delayTime)
    -- 锁屏一段时间
    local lockPanelNode = self:getLockPanelParent();
    lockPanelNode:setVisible(true);
    performWithDelay(lockPanelNode, function ()
        lockPanelNode:setVisible(false);
    end, delayTime);
end

-- 玩家受创
function UIDungeonMain:playHeroDamaged(params)
    local damage = params["damage"];
    self:updateUIByDesc("hp");

    local damageNode = findChildByName(self.node, "CT2/damage");
    local effectParent = self:getEffectParent();
    local pos = self:getHeroPos();

    -- 飘字
    local text = damageNode:clone();
    text:setVisible(true);
    text:setString("." .. damage);
    text:setPosition(cc.pAdd(pos, cc.p(0, 10)));

    local callfunc = cc.CallFunc:create(function()
        effectParent:removeChild(text);
    end);

    local move = cc.JumpBy:create(0.5, cc.p(50, 40), 60, 1);
    local action = cc.Sequence:create(move, callfunc);
    text:runAction(action);
    effectParent:addChild(text);

    local hpLabel = findChildByName(self.node, "CT2/hp_val");
    commonTextEffect(hpLabel, TextStyleM.TEXT_COLOR_LIGHT_RED);
end

-- 尝试播放迷宫动作
function UIDungeonMain:tryPlayDungeonCommonActions()
    -- 延迟0.01秒后再检查，否则可能格子和英雄动作还没插入，这里会直接判定可以显示完美击杀。

    if self.playDungeonActionNode == nil then
        self.playDungeonActionNode = cc.Node:create();
        self:addChild(self.playDungeonActionNode);
    end

    local function doTry()
        -- 每个动作完成后，检查下是否所有格子和出战英雄的动作均已完成
        -- 如果都已经完成，则播放迷宫动作播放器中的动作
        local allDone = true;
        for _, grid in pairs(self.grids) do
            if not grid.actionManager:isAllDone() then
                allDone = false;
                break;
            end
        end

        if not self.heroActionManager:isAllDone() then
            allDone = false;
        end

        if allDone then
            self.dungeonActionManager:play();
        end
    end

    self.playDungeonActionNode:stopAllActions();
    performWithDelay(self.playDungeonActionNode, doTry, 0.01);
end

-- 显示完美击杀
function UIDungeonMain:showClearMonster(gainSP)
    if not DungeonInstanceM.isInInstance() and not UnrealDungeonM.isInUnreal() then
        local function show()
            local title = getLocStr("level_clear");
            ME.user.monsterClearedTip = nil;
            local uiNotify = UIDungeonClearNotify.create(title, gainSP);
            UIMgr.getCurrentScene():addForm(uiNotify);
        end

        self.dungeonActionManager:addAction(nil, show, "showClearMonster", { }, DUNGEON_ACTION_PHASE_MONSTER_CLEAR);
        self:tryPlayDungeonCommonActions();
    end
end

-- 获取当前层数
function UIDungeonMain:getUICurFloor()
    return self.curFloor;
end

-- 怪物全部清除处理
function UIDungeonMain:monsterClearFunc(gainSP)
    -- 刷新探索点
    self:updateUIByDesc("search_point");

    -- 显示完美击杀
    self:showClearMonster(gainSP);
end

function UIDungeonMain:playSelfDestructEquipEffect()
    -- 播放自销毁装备奖励提示
    local params = ME.user.destructEquip;
    if params then
        for _, info in pairs(params) do
            local type = info.notifyType;
            local bonusList = info.bonusList;
            local classId = info.classId;
            local tip = SelfDestructM.query(classId, "tip");
            if type == "form" then
                -- 提示类型为弹框
                showBonusNotifyForm(tip, bonusList);
            elseif type == "fly" then
                -- 提示类型为飞入包裹
                alert(tip)
                self:playItemsAnimation(bonusList, 0.6);
            elseif type == "slime_talk" then
                local heroId = info.heroId;
                local msgList = FormulaM.invoke("GET_SLIME_TALKS", heroId, "self_destruct");
                EventMgr.fire(event.SLIME_TALK, {
                    ["heroId"] = heroId,
                    ["msgList"] = msgList,
                    ["endCallback"] = function()
                    -- 完成对话后物品飞入
                        self:playItemsAnimation(bonusList, 0.6);
                    end,
                });
            end
        end
        ME.user.destructEquip = nil;
    end
end

function UIDungeonMain:whenSelfBonus(bonus)
    -- 物品
    if bonus[1] == 1 then
        local itemId = bonus[2];
        local num = bonus[3];
        if SpellM.isSpell(itemId) then
            playSpellGainEffectOnEnter(self:getSpellNode(), self:getHeroPos(), itemId, num);
        elseif EquipM.isEquipment(itemId) or PropertyM.isProperty(itemId) then
            playEquipGainEffectOnEnter(self:getEquipNode(), self:getHeroPos(), itemId, num);
        else
            playUsePropertyGetItem(bonus);
        end
    elseif bonus[1] == 2 then -- 属性
        local effectNode = self:getEffectParent();
        if bonus[2] == "hp" or bonus[2] == "max_hp" or bonus[2] == "limit_hp" then
            -- 需要处理下奖励数值
            local count = bonus[3];
            playHpGainEffectOnEnter(effectNode, self:getHeroPos(), self:getHpTextNode(), self:getHpIconNode(), count);
            performWithDelay(self, function() self:updateUIByDesc("hp"); end, 1.5);
        elseif bonus[2] == "mp" or bonus[2] == "max_mp" or bonus[2] == "limit_mp" then
            -- 需要处理下奖励数值
            local count = bonus[3];
            playMpGainEffectOnEnter(effectNode, self:getHeroPos(), self:getMpTextNode(), self:getMpIconNode(), count);
            performWithDelay(self, function() self:updateUIByDesc("mp"); end, 1.5);
        elseif bonus[2] == "attack" then
            local count = bonus[3];
            playAttackGainEffectOnEnter(effectNode, self:getHeroPos(), self:getAttackTextNode(), self:getAttackIconNode(), count);
            performWithDelay(self, function() self:updateUIByDesc("attack"); end, 1.5);
        elseif bonus[2] == "magic" then
            local count = bonus[3];
            playMagicGainEffectOnEnter(effectNode, self:getHeroPos(), self:getMagicTextNode(), self:getMagicIconNode(), count);
            performWithDelay(self, function() self:updateUIByDesc("magic"); end, 1.5);
        elseif bonus[2] == "search_point" then
            playSpGainEffectOnEnter(effectNode, self:getHeroPos(), self.dungeonTopMenu:getSpTextNode(), self.dungeonTopMenu:getSpIconNode(), bonus[3]);
        else
            playUsePropertyGetItem(bonus);
        end
    end
end

-- 设置时间停止需要重刷标记
function UIDungeonMain:setTimeCeaseNeedRefresh(flag)
    self.timeCeaseNeedRefresh = true;
end

-- 每回合检查下是否有时间停止
function UIDungeonMain:checkTimeCeaseEffect()
    -- 检查玩家身上的stop_action属性
    local props = PropM.fetchProps(ME.user, "stop_action");
    if #props == 0 then
        if self.isTimeCeasing then
            -- 停止时间停止效果
            stopTimeCeaseEffect();
        end
        return;
    end

    -- 身上只会存在一个
    local prop = props[1];

    -- 剩余回合数
    local leftRound = prop[4] - CombatM.getRound();
    if leftRound < 0 then
        leftRound = 0;
    end

    if leftRound > 0 then
        if not self.isTimeCeasing or self.timeCeaseNeedRefresh then
            local effectParent = self:getEffectParent();
            if not UIDungeonMgr:getCurLevel().timeCeaseEffect then
                -- 暂离重进时，特效已经释放掉，需要重新创建
                local _, effect = playEffect(effectParent, 35, 320, 480);
                effect.action:gotoFrameAndPause(80);
                UIDungeonMgr:getCurLevel().timeCeaseEffect = effect;
            end

            if not UIDungeonMgr:getCurLevel().timeCeaseLabel then
                -- 暂离重进时，文字已经释放掉，需要重新创建
                local textLabel = ccui.Text:create();
                TextStyleM.setTextStyle(textLabel, TextStyleM.TEXT_SIZE_LARGE3, TextStyleM.TEXT_COLOR_WHITE);
                UIDungeonMgr:getCurLevel().timeCeaseLabel = textLabel;
                textLabel:setPosition(320, 480);
                effectParent:addChild(textLabel);
                textLabel:setOpacity(150);
            end

            -- 启动时间停止效果
            playTimeCeaseEffect();
        end

        if UIDungeonMgr:getCurLevel().timeCeaseLabel then
            UIDungeonMgr:getCurLevel().timeCeaseLabel:setString(getRomanNumeral(leftRound));
        end
    else
        -- 停止时间停止效果
        stopTimeCeaseEffect();
    end
end

-- 初始化时，检查下是否有怪物处于石化状态
function UIDungeonMain:checkIsInStone()
    local grids = self:getAllGrids();
    for i, grid in pairs(grids) do
        local monster = grid.gridData.monster;
        if monster then
            local props = PropM.fetchProps(monster, "petrifaction");
            if #props > 0 then
                local pos = grid.gridData:getPos();
                petrifiedTarget(pos, true);
            end
        end
    end
end

-- 在断线重登或者第一次进入地牢界面时，检查下出战英雄是否处于变身状态
function UIDungeonMain:checkShapeChangeEffect()
    -- 检查玩家身上的shape_change状态
    local props = PropM.fetchProps(ME.user, "shape_change");
    for _, prop in ipairs(props) do
        -- 查询变身信息
        local shapeId = prop[2];
        local shapeInfo = ShapeChangeM.query(shapeId);

        -- 没有查询到
        if not shapeInfo then
            return;
        end

        -- 获取当前的出战宠物
        local pet = ME.user:getActivePet();
        if nil == pet then
            return;
        end

        -- 不是需求变身的宠物
        local userPetId = pet.classId;
        local needId = shapeInfo["class_id"];
        if needId ~= userPetId then
            ME.user.dbase:deleteTemp("play_trans_effect");
            return;
        end

        -- 如果已经变身过，不再变身
        if ME.user.dbase:queryTemp("play_trans_effect") ~= 1 then
            return;
        end

        -- 根据条件判断是或否播放变身特效
        local isPlayEffect;
        local condition = prop[3];
        local tag = ShapeChangeM.parseCondition(condition);
        if tag == ShapeChangeM.CHANGE_REPEATEDLY or tag == ShapeChangeM.REPEATEDLY_LATER then
            isPlayEffect = true;
        end

        -- 播放特效
        self:playActivePetShapeChangeEffect(shapeId, isPlayEffect);
        ShapeChangeM.setInShapeChange(true);

        -- 清除标记
        ME.user.dbase:deleteTemp("play_trans_effect");
        return;
    end
end

-- 离线重登或暂离结束后检查下是否BOSS层的流浪者数量
function UIDungeonMain:checkTrampInBossLayer()
    -- 只检查boss层的情况
    if not DungeonBossM.isInBossLayer() then
        return;
    end

    BossTentM.updateTrampNum();
end

-- 一些入场的特殊显示
function UIDungeonMain:specialEnterEffect()
    if self.hasSpecialEnter then
        return;
    end

    if not ActivityDungeonM.isInActivityBossLayer() then
        return;
    end

    -- 入场延迟召棋子
    if type(ME.user.chessBossAppearSummon) == "table" then
        -- 锁屏
        self:lockScreen(3);
        self.hasSpecialEnter = true;

        local arr = table.deepcopy(ME.user.chessBossAppearSummon);
        for index, pos in pairs(arr) do
            local function delay()
                ME.user.chessBossAppearSummon = nil;
                -- 静默开格子/格子转换
                local grid = self:getAllGrids()[pos];
                self:openGrid(pos, grid.type, true);
                grid:onTransformGrid(pos, "chess_appear", 1320);
            end
            performWithDelay(self.node, delay, 1.5 + 0.3 * index);
        end
    end
end

-- 检查需要隐藏的一些界面
function UIDungeonMain:checkNeedHideUI()
    -- 顶部菜单
    local uiTopMenu = UIMgr:getCurrentScene().topMenu;
    if uiTopMenu and uiTopMenu.lastSecondFrame == TOPMENU_FRAME_ENGINEERING_MANUAL then
        uiTopMenu:setVisible(false);
    end
end

-- 属性增加
function UIDungeonMain:attribAdd(args)
    if type(args) ~= "table" then
        return;
    end

    local effectNode = self:getEffectParent();
    for _, attrib in ipairs(table.keys(args)) do
        if attrib == "hp" or attrib == "max_hp" or attrib == "limit_hp" then
            local count = args[attrib];
            playHpGainEffectOnEnter(effectNode, self:getHeroPos(), self:getHpTextNode(), self:getHpIconNode(), count);
           -- performWithDelay(self, function() self:updateUIByDesc("hp"); end, 1.5);
        elseif attrib == "mp" or attrib == "max_mp" or attrib == "limit_mp" then
            local count = args[attrib];
            playMpGainEffectOnEnter(effectNode, self:getHeroPos(), self:getMpTextNode(), self:getMpIconNode(), count);
          --  performWithDelay(self, function() self:updateUIByDesc("mp"); end, 1.5);
        elseif attrib == "attack" then
            local count = args[attrib];
            playAttackGainEffectOnEnter(effectNode, self:getHeroPos(), self:getAttackTextNode(), self:getAttackIconNode(), count);
          --  performWithDelay(self, function() self:updateUIByDesc("attack"); end, 1.5);
        elseif attrib == "magic" then
            local count = args[attrib];
            playMagicGainEffectOnEnter(effectNode, self:getHeroPos(), self:getMagicTextNode(), self:getMagicIconNode(), count);
           -- performWithDelay(self, function() self:updateUIByDesc("magic"); end, 1.5);
        end
    end
end

-- 更新魔藤血量/攻击
function UIDungeonMain:redrawBineAttackAndHp()
    -- 如果有魔藤，刷新其攻击和血量显示
    local bine = ME.user.bine;
    if bine and bine.classId == PLANT_CLASS_BINE
        and not DungeonInstanceM.isInInstance() then
        local pos = bine:getPos();
        local grid = self.grids[pos];
        if grid then
            grid:redrawBineAttackAndHp();
        end
    end
end

-- 执行天空战扣除血量
function UIDungeonMain:doSkyCombatCostHp()
    if not self.skyCombatCostHp then
        return;
    end

    EventMgr.fire(event.PLAYER_MOVE, { });

    -- 血量扣除结束后
    self.skyCombatCostHp = nil;

    -- 播放下甲板爆炸的光效
    local effectNode = findChildByName(self.node, "effect_node");
    playEffect(effectNode, 1701, 160, 640);
    performWithDelay(effectNode, function ()
        playEffect(effectNode, 1701, 480, 580)
    end, 0.2);
end

-- 检查电流装置情况
function UIDungeonMain:checkInterfereDevice()
    local info = MagicStoneM.getStoneInfo();
    if not info or info["id"] ~= 8
        or info["state"] == GRID_STATE_DISABLE then
        return;
    end

    interfereDeviceEffect();

    AlertStateM.setState(AlertStateM.ALERT_STATE_INTERFERE_DEVICE);
end

-- 检查冰霜泰坦警戒情况
function UIDungeonMain:checkIceTitantAlert()
    local showAlert = true;

    -- 获取boss位置
    local pos = DungeonBossM.getBossPos();
    if pos == -1 then
        return;
    end

    -- 如果处于变形术状态，那么移除警戒效果
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        showAlert = false;
    end

    local monster = grid.monster;
    if not monster or monster:isDead() then
        showAlert = false;
    end

    if #PropM.fetchProps(monster, "transfiguration") > 0
        or #PropM.fetchProps(monster, "skeleton") > 0 then
        showAlert = false;
    end

    -- 更新警戒
    if showAlert then
        AlertStateM.setState(AlertStateM.ALERT_STATE_INTERFERE_DEVICE);
    else
        AlertStateM.clearState(AlertStateM.ALERT_STATE_INTERFERE_DEVICE);
    end
end
